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