cf-uaa-lib 1.3.1 → 1.3.2

Sign up to get free protection for your applications and to get access to all the features.
data/NOTICE.TXT ADDED
@@ -0,0 +1,10 @@
1
+ Cloud Foundry 2012.02.03 Beta
2
+ Copyright (c) [2009-2012] VMware, Inc. All Rights Reserved.
3
+
4
+ This product is licensed to you under the Apache License, Version 2.0 (the "License").
5
+ You may not use this product except in compliance with the License.
6
+
7
+ This product includes a number of subcomponents with
8
+ separate copyright notices and license terms. Your use of these
9
+ subcomponents is subject to the terms and conditions of the
10
+ subcomponent's license, as noted in the LICENSE file.
data/README.md CHANGED
@@ -2,6 +2,8 @@
2
2
 
3
3
  Client gem for interacting with the [CloudFoundry UAA server](https://github.com/cloudfoundry/uaa)
4
4
 
5
+ For documentation see: https://rubygems.org/gems/cf-uaa-lib
6
+
5
7
  ## Install from rubygems
6
8
 
7
9
  $ gem install cf-uaa-lib
@@ -17,7 +19,7 @@ Client gem for interacting with the [CloudFoundry UAA server](https://github.com
17
19
  #!/usr/bin/env ruby
18
20
  require 'uaa'
19
21
  token_issuer = CF::UAA::TokenIssuer.new("https://uaa.cloudfoundry.com", "vmc")
20
- puts token\_issuer.prompts.inspect
22
+ puts token_issuer.prompts.inspect
21
23
  token = token_issuer.implicit_grant_with_creds(username: "<your_username>", password: "<your_password>")
22
24
  token_info = TokenCoder.decode(token.info["access_token"], nil, nil, false) #token signature not verified
23
25
  puts token_info["user_name"]
data/Rakefile CHANGED
@@ -10,10 +10,10 @@
10
10
  # subcomponent's license, as noted in the LICENSE file.
11
11
  #
12
12
 
13
- require "rdoc/task"
14
13
  require "rspec/core/rake_task"
15
14
  require "bundler/gem_tasks" # only available in bundler >= 1.0.15
16
15
  require "ci/reporter/rake/rspec"
16
+ require "yard"
17
17
 
18
18
  ENV['CI_REPORTS'] = File.expand_path("spec_reports")
19
19
  COV_REPORTS = File.expand_path("coverage")
@@ -22,16 +22,17 @@ task :default => [:test]
22
22
  task :tests => [:test]
23
23
  task :spec => [:test]
24
24
 
25
+ YARD::Rake::YardocTask.new do |t|
26
+ t.files = ['lib/**/*.rb', '-', 'LICENSE.TXT', 'NOTICE.TXT']
27
+ t.options = ['--main', 'README.md', '--no-private',
28
+ '--title', 'Cloud Foundry UAA Client API']
29
+ end
30
+
25
31
  RSpec::Core::RakeTask.new("test") do |t|
26
32
  t.rspec_opts = ["--format", "documentation", "--colour"]
27
33
  t.pattern = "spec/**/*_spec.rb"
28
34
  end
29
35
 
30
- RDoc::Task.new do |rd|
31
- rd.rdoc_files.include("lib/**/*.rb")
32
- rd.rdoc_dir = "doc"
33
- end
34
-
35
36
  task :ci => [:pre_coverage, :rcov_reports, "ci:setup:rspec", :test]
36
37
  task :cov => [:pre_coverage, :test, :view_coverage]
37
38
  task :coverage => [:pre_coverage, :test]
data/cf-uaa-lib.gemspec CHANGED
@@ -36,10 +36,12 @@ Gem::Specification.new do |s|
36
36
 
37
37
  s.add_development_dependency "bundler"
38
38
  s.add_development_dependency "rake"
39
- s.add_development_dependency "rdoc"
39
+ s.add_development_dependency "yard"
40
+ s.add_development_dependency "redcarpet"
40
41
  s.add_development_dependency "rspec"
41
42
  s.add_development_dependency "simplecov"
42
43
  s.add_development_dependency "simplecov-rcov"
43
44
  s.add_development_dependency "ci_reporter"
45
+ s.add_development_dependency "json_pure"
44
46
 
45
47
  end
data/lib/uaa/http.rb CHANGED
@@ -17,19 +17,19 @@ require 'uaa/util'
17
17
 
18
18
  module CF::UAA
19
19
 
20
- # Indicates URL for the target is bad or not accessible
20
+ # Indicates URL for the target is bad or not accessible.
21
21
  class BadTarget < UAAError; end
22
22
 
23
- # Error indicating the resource within the target server was not found
23
+ # Indicates the resource within the target server was not found.
24
24
  class NotFound < UAAError; end
25
25
 
26
26
  # Indicates a syntax error in a response from the UAA, e.g. missing required response field.
27
27
  class BadResponse < UAAError; end
28
28
 
29
- # Indicates a token is malformed or expired
29
+ # Indicates a token is malformed or expired.
30
30
  class InvalidToken < UAAError; end
31
31
 
32
- # Indicates an error from the http client stack
32
+ # Indicates an error from the http client stack.
33
33
  class HTTPException < UAAError; end
34
34
 
35
35
  # An application level error from the UAA which includes error info in the reply.
@@ -43,46 +43,51 @@ end
43
43
  # Utility accessors and methods for objects that want to access JSON web APIs.
44
44
  module Http
45
45
 
46
- # Sets the current logger instance to recieve error messages
46
+ # Sets the current logger instance to recieve error messages.
47
+ # @param [Logger] logr
48
+ # @return [Logger]
47
49
  def logger=(logr); @logger = logr end
48
50
 
49
- # Returns the current logger or CF::UAA::Util.default_logger is none has been set.
50
- def logger ; @logger ||= Util.default_logger end
51
+ # The current logger or {Util.default_logger} if none has been set.
52
+ # @return [Logger]
53
+ def logger ; @logger || Util.default_logger end
51
54
 
52
- # Returns true if the current logger is set to +:trace+ level
53
- def trace? ; @logger && @logger.respond_to?(:trace?) && @logger.trace? end
55
+ # Indicates if the current logger is set to +:trace+ level.
56
+ # @return [Boolean]
57
+ def trace? ; (lgr = logger).respond_to?(:trace?) && lgr.trace? end
54
58
 
55
- # Sets handler for outgoing http requests. If not set, an internal cache of
56
- # net/http connections is used.
57
- # Arguments to handler are url, method, body, headers.
58
- def set_request_handler(&blk) @req_handler = blk end
59
+ # Sets a handler for outgoing http requests. If no handler is set, an
60
+ # internal cache of net/http connections is used. Arguments to the handler
61
+ # are url, method, body, headers.
62
+ # @param [Proc] blk handler block
63
+ # @return [nil]
64
+ def set_request_handler(&blk) @req_handler = blk; nil end
59
65
 
60
- # Returns a string for use in an http basic authentication header
66
+ # Constructs an http basic authentication header.
67
+ # @return [String]
61
68
  def self.basic_auth(name, password)
62
- "Basic " + Base64::strict_encode64("#{name}:#{password}")
69
+ str = "#{name}:#{password}"
70
+ "Basic " + (Base64.respond_to?(:strict_encode64)?
71
+ Base64.strict_encode64(str): [str].pack("m").gsub(/\n/, ''))
63
72
  end
64
73
 
65
74
  private
66
75
 
67
- def add_auth_json(auth, headers, jsonhdr = "content-type")
68
- headers["authorization"] = auth if auth
69
- headers.merge!(jsonhdr => "application/json")
76
+ def json_get(target, path = nil, style = nil, headers = {})
77
+ raise ArgumentError unless style.nil? || style.is_a?(Symbol)
78
+ json_parse_reply(style, *http_get(target, path, headers.merge("accept" => "application/json")))
70
79
  end
71
80
 
72
- def json_get(target, path = nil, authorization = nil, key_style = :none, headers = {})
73
- json_parse_reply(*http_get(target, path,
74
- add_auth_json(authorization, headers, "accept")), key_style)
81
+ def json_post(target, path, body, headers = {})
82
+ http_post(target, path, Util.json(body), headers.merge("content-type" => "application/json"))
75
83
  end
76
84
 
77
- def json_post(target, path, body, authorization, headers = {})
78
- http_post(target, path, Util.json(body), add_auth_json(authorization, headers))
85
+ def json_put(target, path, body, headers = {})
86
+ http_put(target, path, Util.json(body), headers.merge("content-type" => "application/json"))
79
87
  end
80
88
 
81
- def json_put(target, path, body, authorization = nil, headers = {})
82
- http_put(target, path, Util.json(body), add_auth_json(authorization, headers))
83
- end
84
-
85
- def json_parse_reply(status, body, headers, key_style = :none)
89
+ def json_parse_reply(style, status, body, headers)
90
+ raise ArgumentError unless style.nil? || style.is_a?(Symbol)
86
91
  unless [200, 201, 204, 400, 401, 403].include? status
87
92
  raise (status == 404 ? NotFound : BadResponse), "invalid status response: #{status}"
88
93
  end
@@ -90,8 +95,8 @@ module Http
90
95
  headers["content-type"] !~ /application\/json/i)
91
96
  raise BadResponse, "received invalid response content or type"
92
97
  end
93
- parsed_reply = Util.json_parse(body, key_style)
94
- if status >= 400
98
+ parsed_reply = Util.json_parse(body, style)
99
+ if status >= 400
95
100
  raise parsed_reply && parsed_reply["error"] == "invalid_token" ?
96
101
  InvalidToken : TargetError.new(parsed_reply), "error response"
97
102
  end
@@ -131,8 +136,8 @@ module Http
131
136
  end
132
137
 
133
138
  def net_http_request(url, method, body, headers)
134
- raise ArgumentError unless reqtype = {delete: Net::HTTP::Delete,
135
- get: Net::HTTP::Get, post: Net::HTTP::Post, put: Net::HTTP::Put}[method]
139
+ raise ArgumentError unless reqtype = {:delete => Net::HTTP::Delete,
140
+ :get => Net::HTTP::Get, :post => Net::HTTP::Post, :put => Net::HTTP::Put}[method]
136
141
  headers["content-length"] = body.length if body
137
142
  uri = URI.parse(url)
138
143
  req = reqtype.new(uri.request_uri)
data/lib/uaa/misc.rb CHANGED
@@ -15,7 +15,7 @@ require 'uaa/http'
15
15
 
16
16
  module CF::UAA
17
17
 
18
- # interfaces to UAA endpoints that are not in the context
18
+ # Provides interfaces to various UAA endpoints that are not in the context
19
19
  # of an overall class of operations like SCIM resources or OAuth2 tokens.
20
20
  class Misc
21
21
 
@@ -23,55 +23,84 @@ class Misc
23
23
  include Http
24
24
  end
25
25
 
26
- # Returns a hash of information about the user authenticated by the token in
27
- # the +auth_header+. It calls the +/userinfo+ endpoint and returns a hash of
28
- # user information as specified by OpenID Connect.
29
- # See: http://openid.net/connect/
30
- # Specifically: http://openid.net/specs/openid-connect-standard-1_0.html#userinfo_ep
31
- # and: http://openid.net/specs/openid-connect-messages-1_0.html#anchor9
32
- def self.whoami(target, auth_header)
33
- json_get(target, "/userinfo?schema=openid", auth_header)
26
+ # sets whether the keys in returned hashes should be symbols.
27
+ # @return [Boolean] the new state
28
+ def self.symbolize_keys=(bool) !!(@key_style = bool ? :sym : nil) end
29
+
30
+ # Gets information about the user authenticated by the token in the
31
+ # +auth_header+. It GETs from the +target+'s +/userinfo+ endpoint and
32
+ # returns user information as specified by OpenID Connect.
33
+ # @see http://openid.net/connect/
34
+ # @see http://openid.net/specs/openid-connect-standard-1_0.html#userinfo_ep
35
+ # @see http://openid.net/specs/openid-connect-messages-1_0.html#anchor9
36
+ # @param (see Misc.server)
37
+ # @param [String] auth_header see {TokenInfo#auth_header}
38
+ # @return [Hash]
39
+ def self.whoami(target, auth_header)
40
+ json_get(target, "/userinfo?schema=openid", @key_style, "authorization" => auth_header)
34
41
  end
35
42
 
36
- # Returns a hash of various monitoring and status variables from the UAA.
37
- # Authenticates to the UAA with basic authentication. Name and pwd
38
- # must be configured in the UAA.
39
- def self.varz(target, name, pwd)
40
- json_get(target, "/varz", Http.basic_auth(name, pwd))
43
+ # Gets various monitoring and status variables from the server.
44
+ # Authenticates using +name+ and +pwd+ for basic authentication.
45
+ # @param (see Misc.server)
46
+ # @return [Hash]
47
+ def self.varz(target, name, pwd)
48
+ json_get(target, "/varz", @key_style, "authorization" => Http.basic_auth(name, pwd))
41
49
  end
42
50
 
43
- # returns a hash of basic information about the target server, including
44
- # version number, commit ID, and links to API endpoints.
51
+ # Gets basic information about the target server, including version number,
52
+ # commit ID, and links to API endpoints.
53
+ # @param [String] target The base URL of the server. For example the target could
54
+ # be {https://login.cloudfoundry.com}, {https://uaa.cloudfoundry.com}, or
55
+ # {http://localhost:8080/uaa}.
56
+ # @return [Hash]
45
57
  def self.server(target)
46
- reply = json_get(target, '/login')
47
- return reply if reply && reply["prompts"]
58
+ reply = json_get(target, '/login', @key_style)
59
+ return reply if reply && (reply[:prompts] || reply['prompts'])
48
60
  raise BadResponse, "Invalid response from target #{target}"
49
61
  end
50
62
 
63
+ # Gets the key from the server that is used to validate token signatures. If
64
+ # the server is configured to use a symetric key, the caller must authenticate
65
+ # by providing a a +client_id+ and +client_secret+. If the server
66
+ # is configured to sign with a private key, this call will retrieve the
67
+ # public key and +client_id+ must be nil.
68
+ # @param (see Misc.server)
69
+ # @return [Hash]
51
70
  def self.validation_key(target, client_id = nil, client_secret = nil)
52
- json_get(target, "/token_key", (client_id && client_secret ? Http.basic_auth(client_id, client_secret) : nil))
71
+ hdrs = client_id && client_secret ?
72
+ { "authorization" => Http.basic_auth(client_id, client_secret)} : {}
73
+ json_get(target, "/token_key", @key_style, hdrs)
53
74
  end
54
75
 
55
- # Sends the token to the UAA to validate. Returns hash of values that are
56
- # associated with the token. Authenticates with client_id and client_secret.
57
- # If audience_ids are specified, raises AuthError token is not for this
58
- # audience -- i.e. the token's 'aud' attribute does not contain one or more
59
- # of the specified audience_ids.
76
+ # Sends +token+ to the server to validate and decode. Authenticates with
77
+ # +client_id+ and +client_secret+. If +audience_ids+ are specified and the
78
+ # token's "aud" attribute does not contain one or more of the audience_ids,
79
+ # raises AuthError -- meaning the token is not for this audience.
80
+ # @param (see Misc.server)
81
+ # @param [String] token an access token as retrieved by {TokenIssuer}. See
82
+ # also {TokenInfo}.
83
+ # @param [String] token_type as retrieved by {TokenIssuer}. See {TokenInfo}.
84
+ # @return [Hash] contents of the token
60
85
  def self.decode_token(target, client_id, client_secret, token, token_type = "bearer", audience_ids = nil)
61
86
  reply = json_get(target, "/check_token?token_type=#{token_type}&token=#{token}",
62
- Http.basic_auth(client_id, client_secret))
63
- auds = Util.arglist(reply["aud"])
87
+ @key_style, "authorization" => Http.basic_auth(client_id, client_secret))
88
+ auds = Util.arglist(reply[:aud] || reply['aud'])
64
89
  if audience_ids && (!auds || (auds & audience_ids).empty?)
65
90
  raise AuthError, "invalid audience: #{auds.join(' ')}"
66
91
  end
67
92
  reply
68
93
  end
69
94
 
70
- # Returns a hash of information about the given password, including a
71
- # strength score and an indication of what strength it required by the UAA.
95
+ # Gets information about the given password, including a strength score and
96
+ # an indication of what strength is required.
97
+ # @param (see Misc.server)
98
+ # @return [Hash]
72
99
  def self.password_strength(target, password)
73
- json_parse_reply(*request(target, :post, '/password/score', URI.encode_www_form("password" => password),
74
- "content-type" => "application/x-www-form-urlencoded", "accept" => "application/json"))
100
+ json_parse_reply(@key_style, *request(target, :post, '/password/score',
101
+ Util.encode_form(:password => password),
102
+ "content-type" => "application/x-www-form-urlencoded",
103
+ "accept" => "application/json"))
75
104
  end
76
105
 
77
106
  end
data/lib/uaa/scim.rb CHANGED
@@ -19,7 +19,20 @@ module CF::UAA
19
19
  # Client Registrations. It provides access to the SCIM endpoints on the UAA.
20
20
  # For more information about SCIM -- the IETF's System for Cross-domain
21
21
  # Identity Management (formerly known as Simple Cloud Identity Management) --
22
- # see http://www.simplecloud.info
22
+ # see {http://www.simplecloud.info}.
23
+ #
24
+ # The types of objects and links to their schema are as follows:
25
+ # * +:user+ -- {http://www.simplecloud.info/specs/draft-scim-core-schema-01.html#user-resource}
26
+ # or {http://www.simplecloud.info/specs/draft-scim-core-schema-01.html#anchor8}
27
+ # * +:group+ -- {http://www.simplecloud.info/specs/draft-scim-core-schema-01.html#group-resource}
28
+ # or {http://www.simplecloud.info/specs/draft-scim-core-schema-01.html#anchor10}
29
+ # * +:client+
30
+ # * +:user_id+ -- {https://github.com/cloudfoundry/uaa/blob/master/docs/UAA-APIs.rst#converting-userids-to-names}
31
+ #
32
+ # Naming attributes by type of object:
33
+ # * +:user+ is "username"
34
+ # * +:group+ is "displayname"
35
+ # * +:client+ is "client_id"
23
36
  class Scim
24
37
 
25
38
  include Http
@@ -49,13 +62,15 @@ class Scim
49
62
  def force_case(obj)
50
63
  return obj.collect {|o| force_case(o)} if obj.is_a? Array
51
64
  return obj unless obj.is_a? Hash
52
- obj.each_with_object({}) {|(k, v), h| h[force_attr(k)] = force_case(v) }
65
+ new_obj = {}
66
+ obj.each {|(k, v)| new_obj[force_attr(k)] = force_case(v) }
67
+ new_obj
53
68
  end
54
69
 
55
70
  # an attempt to hide some scim and uaa oddities
56
71
  def type_info(type, elem)
57
- scimfo = {user: ["/Users", "userName"], group: ["/Groups", "displayName"],
58
- client: ["/oauth/clients", 'client_id'], user_id: ["/ids/Users", 'userName']}
72
+ scimfo = {:user => ["/Users", "userName"], :group => ["/Groups", "displayName"],
73
+ :client => ["/oauth/clients", 'client_id'], :user_id => ["/ids/Users", 'userName']}
59
74
  unless elem == :path || elem == :name_attr
60
75
  raise ArgumentError, "scim schema element must be :path or :name_attr"
61
76
  end
@@ -65,174 +80,199 @@ class Scim
65
80
  ary[elem == :path ? 0 : 1]
66
81
  end
67
82
 
68
- def prep_request(type, info = nil)
69
- [type_info(type, :path), force_case(info)]
83
+ def jkey(k) @key_style == :down ? k.to_s : k end
84
+
85
+ def fake_client_id(info)
86
+ idk, ck = jkey(:id), jkey(:client_id)
87
+ info[idk] = info[ck] if info[ck] && !info[idk]
70
88
  end
71
89
 
72
90
  public
73
91
 
74
- # The +auth_header+ parameter refers to a string that can be used in an
75
- # authorization header. For OAuth2 with JWT tokens this would be something
76
- # like "bearer xxxx.xxxx.xxxx". The Token class provides
77
- # CF::UAA::Token#auth_header for this purpose.
78
- def initialize(target, auth_header) @target, @auth_header = target, auth_header end
79
-
80
- # creates a SCIM resource. For possible values for the +type+ parameter, and links
81
- # to the schema of each type see #query
82
- # info is a hash structure converted to json and sent to the scim endpoint
83
- # A hash of the newly created object is returned, including its ID
84
- # and meta data.
85
- def add(type, info)
86
- path, info = prep_request(type, info)
87
- reply = json_parse_reply(*json_post(@target, path, info, @auth_header), :down)
88
-
89
- # hide client endpoints that are not scim compatible
90
- reply['id'] = reply['client_id'] if type == :client && reply['client_id'] && !reply['id']
92
+ # @param (see Misc.server)
93
+ # @param [String] auth_header a string that can be used in an
94
+ # authorization header. For OAuth2 with JWT tokens this would be something
95
+ # like "bearer xxxx.xxxx.xxxx". The {TokenInfo} class provides
96
+ # {TokenInfo#auth_header} for this purpose.
97
+ # @param style (see Util.hash_key)
98
+ def initialize(target, auth_header, options = {})
99
+ @target, @auth_header = target, auth_header
100
+ @key_style = options[:symbolize_keys] ? :downsym : :down
101
+ end
91
102
 
92
- return reply if reply && reply["id"]
93
- raise BadResponse, "no id returned by add request to #{@target}#{path}"
103
+ # Creates a SCIM resource.
104
+ # @param [Symbol] type can be :user, :group, :client, :user_id.
105
+ # @param [Hash] info converted to json and sent to the scim endpoint. For schema of
106
+ # each type of object see {Scim}.
107
+ # @return [Hash] contents of the object, including its +id+ and meta-data.
108
+ def add(type, info)
109
+ path, info = type_info(type, :path), force_case(info)
110
+ reply = json_parse_reply(@key_style, *json_post(@target, path, info,
111
+ "authorization" => @auth_header))
112
+ fake_client_id(reply) if type == :client # hide client reply, not quite scim
113
+ reply
94
114
  end
95
115
 
96
- # Deletes a SCIM resource identified by +id+. For possible values for type, see #query
116
+ # Deletes a SCIM resource
117
+ # @param type (see #add)
118
+ # @param [String] id the id attribute of the SCIM object
119
+ # @return [nil]
97
120
  def delete(type, id)
98
- path, _ = prep_request(type)
99
- http_delete @target, "#{path}/#{URI.encode(id)}", @auth_header
121
+ http_delete @target, "#{type_info(type, :path)}/#{URI.encode(id)}", @auth_header
100
122
  end
101
123
 
102
- # +info+ is a hash structure converted to json and sent to a scim endpoint
103
- # For possible types, see #query
124
+ # Replaces the contents of a SCIM object.
125
+ # @param (see #add)
126
+ # @return (see #add)
104
127
  def put(type, info)
105
- path, info = prep_request(type, info)
128
+ path, info = type_info(type, :path), force_case(info)
106
129
  ida = type == :client ? 'client_id' : 'id'
107
- raise ArgumentError, "scim info must include #{ida}" unless id = info[ida]
108
- hdrs = info && info["meta"] && info["meta"]["version"] ?
109
- {'if-match' => info["meta"]["version"]} : {}
110
- reply = json_parse_reply(*json_put(@target, "#{path}/#{URI.encode(id)}",
111
- info, @auth_header, hdrs), :down)
130
+ raise ArgumentError, "info must include #{ida}" unless id = info[ida]
131
+ hdrs = {'authorization' => @auth_header}
132
+ if info && info['meta'] && (etag = info['meta']['version'])
133
+ hdrs.merge!('if-match' => etag)
134
+ end
135
+ reply = json_parse_reply(@key_style,
136
+ *json_put(@target, "#{path}/#{URI.encode(id)}", info, hdrs))
112
137
 
113
- # hide client endpoints that are not scim compatible
114
- type == :client && !reply ? get(type, info["client_id"]): reply
138
+ # hide client endpoints that are not quite scim compatible
139
+ type == :client && !reply ? get(type, info['client_id']): reply
115
140
  end
116
141
 
117
- # Queries for objects and returns a selected list of attributes for each
118
- # a given filter. Possible values for +type+ and links to the schema of
119
- # corresponding object type are:
120
- # +:user+:: http://www.simplecloud.info/specs/draft-scim-core-schema-01.html#user-resource
121
- # :: http://www.simplecloud.info/specs/draft-scim-core-schema-01.html#anchor8
122
- # +:group+:: http://www.simplecloud.info/specs/draft-scim-core-schema-01.html#group-resource
123
- # :: http://www.simplecloud.info/specs/draft-scim-core-schema-01.html#anchor10
124
- # +:client+::
125
- # +:user_id+:: https://github.com/cloudfoundry/uaa/blob/master/docs/UAA-APIs.rst#converting-userids-to-names
126
- #
127
- # The +query+ hash may contain the following keys:
128
- # attributes:: a comma or space separated list of attribute names to be
129
- # returned for each object that matches the filter. If no attribute
130
- # list is given, all attributes are returned.
131
- # filter:: a filter to select which objects are returned. See
132
- # http://www.simplecloud.info/specs/draft-scim-api-01.html#query-resources
133
- # startIndex:: for paged output, start index of requested result set.
134
- # count:: maximum number of results per reply
142
+ # Gets a set of attributes for each object that matches a given filter.
143
+ # @param (see #add)
144
+ # @param [Hash] query may contain the following keys:
145
+ # * +attributes+: a comma or space separated list of attribute names to be
146
+ # returned for each object that matches the filter. If no attribute
147
+ # list is given, all attributes are returned.
148
+ # * +filter+: a filter to select which objects are returned. See
149
+ # {http://www.simplecloud.info/specs/draft-scim-api-01.html#query-resources}
150
+ # * +startIndex+: for paged output, start index of requested result set.
151
+ # * +count+: maximum number of results per reply
152
+ # @return [Hash] including a +resources+ array of results and
153
+ # pagination data.
135
154
  def query(type, query = {})
136
- path, query = prep_request(type, query)
137
- query = query.reject {|k, v| v.nil? }
155
+ query = force_case(query).reject {|k, v| v.nil? }
138
156
  if attrs = query['attributes']
139
157
  attrs = Util.arglist(attrs).map {|a| force_attr(a)}
140
158
  query['attributes'] = Util.strlist(attrs, ",")
141
159
  end
142
- qstr = query.empty?? '': "?#{URI.encode_www_form(query)}"
143
- info = json_get(@target, "#{path}#{qstr}", @auth_header, :down)
144
- unless info.is_a?(Hash) && info['resources'].is_a?(Array)
160
+ qstr = query.empty?? '': "?#{Util.encode_form(query)}"
161
+ info = json_get(@target, "#{type_info(type, :path)}#{qstr}", @key_style, 'authorization' => @auth_header)
162
+ unless info.is_a?(Hash) && info[rk = jkey(:resources)].is_a?(Array)
145
163
 
146
164
  # hide client endpoints that are not scim compatible
147
- return {'resources' => info.values } if type == :client && info.is_a?(Hash)
165
+ if type == :client && info.is_a?(Hash)
166
+ info.each { |k, v| fake_client_id(v) }
167
+ return {rk => info.values }
168
+ end
148
169
 
149
- raise BadResponse, "invalid reply to query of #{@target}#{path}"
170
+ raise BadResponse, "invalid reply to #{type} query of #{@target}"
150
171
  end
151
172
  info
152
173
  end
153
174
 
154
- # Returns a hash of information about a specific object.
155
- # [type] For possible values of type, see #add
156
- # [id] the id attribute of the object assigned by the UAA
175
+ # Get information about a specific object.
176
+ # @param (see #delete)
177
+ # @return (see #add)
157
178
  def get(type, id)
158
- path, _ = prep_request(type)
159
- info = json_get(@target, "#{path}/#{URI.encode(id)}", @auth_header, :down)
179
+ info = json_get(@target, "#{type_info(type, :path)}/#{URI.encode(id)}",
180
+ @key_style, 'authorization' => @auth_header)
160
181
 
161
- # hide client endpoints that are not scim compatible
162
- info["id"] = info["client_id"] if type == :client && !info["id"]
182
+ fake_client_id(info) if type == :client # hide client reply, not quite scim
163
183
  info
164
184
  end
165
185
 
166
- # Collects all pages of entries from a query, returns array of results.
167
- # For descriptions of the +type+ and +query+ parameters, see #query.
186
+ # Collects all pages of entries from a query
187
+ # @param type (see #query)
188
+ # @param [Hash] query may contain the following keys:
189
+ # * +attributes+: a comma or space separated list of attribute names to be
190
+ # returned for each object that matches the filter. If no attribute
191
+ # list is given, all attributes are returned.
192
+ # * +filter+: a filter to select which objects are returned. See
193
+ # {http://www.simplecloud.info/specs/draft-scim-api-01.html#query-resources}
194
+ # @return [Array] results
168
195
  def all_pages(type, query = {})
169
- query = query.reject {|k, v| v.nil? }
170
- query["startindex"], info = 1, []
196
+ query = force_case(query).reject {|k, v| v.nil? }
197
+ query["startindex"], info, rk = 1, [], jkey(:resources)
171
198
  while true
172
199
  qinfo = query(type, query)
173
- raise BadResponse unless qinfo["resources"]
174
- return info if qinfo["resources"].empty?
175
- info.concat(qinfo["resources"])
176
- return info unless qinfo["totalresults"] && qinfo["totalresults"] > info.length
177
- unless qinfo["startindex"] && qinfo["itemsperpage"]
178
- raise BadResponse, "incomplete pagination data from #{@target}#{path}"
200
+ raise BadResponse unless qinfo[rk]
201
+ return info if qinfo[rk].empty?
202
+ info.concat(qinfo[rk])
203
+ total = qinfo[jkey :totalresults]
204
+ return info unless total && total > info.length
205
+ unless qinfo[jkey :startindex] && qinfo[jkey :itemsperpage]
206
+ raise BadResponse, "incomplete #{type} pagination data from #{@target}"
179
207
  end
180
208
  query["startindex"] = info.length + 1
181
209
  end
182
210
  end
183
211
 
184
- # Queries for objects by name. Returns array of name/id hashes for each
185
- # name found. For possible values of +type+, see #query
212
+ # Gets id/name pairs for given names.
213
+ # @param type (see #add)
214
+ # @param [Array<String>] names. For naming attribute of each object type see {Scim}
215
+ # @return [Array] array of name/id hashes for each object found
186
216
  def ids(type, *names)
187
217
  na = type_info(type, :name_attr)
188
- filter = names.each_with_object([]) { |n, o| o << "#{na} eq \"#{n}\""}
189
- all_pages(type, attributes: "id,#{na}", filter: filter.join(" or "))
218
+ filter = names.map { |n| "#{na} eq \"#{n}\""}
219
+ all_pages(type, :attributes => "id,#{na}", :filter => filter.join(" or "))
190
220
  end
191
221
 
192
- # Convenience method to query for single object by name. Returns its id.
193
- # Raises error if not found. For possible values of +type+, see #query
222
+ # Convenience method to query for single object by name.
223
+ # @param type (see #add)
224
+ # @param [String] name Value of the Scim object's name attribue. For naming
225
+ # attribute of each type of object see {Scim}.
226
+ # @return [String] the +id+ attribute of the object
194
227
  def id(type, name)
195
228
  res = ids(type, name)
196
229
 
197
230
  # hide client endpoints that are not scim compatible
198
- if type == :client && res && res.length > 0
199
- if res.length > 1 || res[0]["id"].nil?
200
- cr = res.find { |o| o['client_id'] && name.casecmp(o['client_id']) == 0 }
201
- return cr['id'] || cr['client_id'] if cr
202
- end
231
+ ik, ck = jkey(:id), jkey(:client_id)
232
+ if type == :client && res && res.length > 0 && (res.length > 1 || res[0][ik].nil?)
233
+ cr = res.find { |o| o[ck] && name.casecmp(o[ck]) == 0 }
234
+ return cr[ik] || cr[ck] if cr
203
235
  end
204
236
 
205
237
  unless res && res.is_a?(Array) && res.length == 1 &&
206
- res[0].is_a?(Hash) && (id = res[0]["id"])
238
+ res[0].is_a?(Hash) && (id = res[0][jkey :id])
207
239
  raise NotFound, "#{name} not found in #{@target}#{type_info(type, :path)}"
208
240
  end
209
241
  id
210
242
  end
211
243
 
212
- # [For a user to change their own password] Token must contain "password.write" scope and the
213
- # correct +old_password+ must be given.
214
- # [For an admin to set a user's password] Token must contain "uaa.admin" scope.
215
- #
216
- # For more information see:
217
- # https://github.com/cloudfoundry/uaa/blob/master/docs/UAA-APIs.rst#change-password-put-useridpassword
218
- # or https://github.com/cloudfoundry/uaa/blob/master/docs/UAA-Security.md#password-change
244
+ # Change password.
245
+ # * For a user to change their own password, the token in @auth_header must
246
+ # contain "password.write" scope and the correct +old_password+ must be given.
247
+ # * For an admin to set a user's password, the token in @auth_header must
248
+ # contain "uaa.admin" scope.
249
+ # @see https://github.com/cloudfoundry/uaa/blob/master/docs/UAA-APIs.rst#change-password-put-useridpassword
250
+ # @see https://github.com/cloudfoundry/uaa/blob/master/docs/UAA-Security.md#password-change
251
+ # @param [String] user_id the {Scim} +id+ attribute of the user
252
+ # @return [Hash] success message from server
219
253
  def change_password(user_id, new_password, old_password = nil)
220
- password_request = {"password" => new_password}
221
- password_request["oldPassword"] = old_password if old_password
222
- json_parse_reply(*json_put(@target, "/Users/#{URI.encode(user_id)}/password", password_request, @auth_header))
254
+ req = {"password" => new_password}
255
+ req["oldPassword"] = old_password if old_password
256
+ json_parse_reply(@key_style, *json_put(@target,
257
+ "#{type_info(:user, :path)}/#{URI.encode(user_id)}/password", req,
258
+ 'authorization' => @auth_header))
223
259
  end
224
260
 
225
- # [For a client to change its own secret] Token must contain "uaa.admin,client.secret" scope and the
226
- # correct +old_secret+ must be given.
227
- # [For an admin to set a client secret] Token must contain "uaa.admin" scope.
228
- #
229
- # For more information see:
230
- # https://github.com/cloudfoundry/uaa/blob/master/docs/UAA-APIs.rst#change-client-secret-put-oauthclientsclient_idsecret
231
- # or https://github.com/cloudfoundry/uaa/blob/master/docs/UAA-Security.md#client-secret-mangagement
261
+ # Change client secret.
262
+ # * For a client to change its own secret, the token in @auth_header must contain
263
+ # "uaa.admin,client.secret" scope and the correct +old_secret+ must be given.
264
+ # * For an admin to set a client secret, the token in @auth_header must contain
265
+ # "uaa.admin" scope.
266
+ # @see https://github.com/cloudfoundry/uaa/blob/master/docs/UAA-APIs.rst#change-client-secret-put-oauthclientsclient_idsecret
267
+ # @see https://github.com/cloudfoundry/uaa/blob/master/docs/UAA-Security.md#client-secret-mangagement
268
+ # @param [String] client_id the {Scim} +id+ attribute of the client
269
+ # @return [Hash] success message from server
232
270
  def change_secret(client_id, new_secret, old_secret = nil)
233
271
  req = {"secret" => new_secret }
234
272
  req["oldSecret"] = old_secret if old_secret
235
- json_parse_reply(*json_put(@target, "/oauth/clients/#{URI.encode(client_id)}/secret", req, @auth_header))
273
+ json_parse_reply(@key_style, *json_put(@target,
274
+ "#{type_info(:client, :path)}/#{URI.encode(client_id)}/secret", req,
275
+ 'authorization' => @auth_header))
236
276
  end
237
277
 
238
278
  end