lib/uaa/scim.rb in cf-uaa-lib-1.3.0 vs lib/uaa/scim.rb in cf-uaa-lib-1.3.1
- old
+ new
@@ -13,23 +13,26 @@
require 'uaa/http'
module CF::UAA
-# This class is for apps that need to manage User Accounts, Groups, or OAuth Client Registrations.
-# It provides access to the SCIM endpoints on the UAA.
+# This class is for apps that need to manage User Accounts, Groups, or OAuth
+# Client Registrations. It provides access to the SCIM endpoints on the UAA.
+# For more information about SCIM -- the IETF's System for Cross-domain
+# Identity Management (formerly known as Simple Cloud Identity Management) --
+# see http://www.simplecloud.info
class Scim
include Http
private
def force_attr(k)
kd = k.to_s.downcase
- kc = {"username" => "userName", "familyname" => "familyName",
- "givenname" => "givenName", "middlename" => "middleName",
- "honorificprefix" => "honorificPrefix",
+ kc = {"username" => "userName", "familyname" => "familyName",
+ "givenname" => "givenName", "middlename" => "middleName",
+ "honorificprefix" => "honorificPrefix",
"honorificsuffix" => "honorificSuffix", "displayname" => "displayName",
"nickname" => "nickName", "profileurl" => "profileUrl",
"streetaddress" => "streetAddress", "postalcode" => "postalCode",
"usertype" => "userType", "preferredlanguage" => "preferredLanguage",
"x509certificates" => "x509Certificates", "lastmodified" => "lastModified",
@@ -60,23 +63,27 @@
raise ArgumentError, "scim resource type must be one of #{scimfo.keys.inspect}"
end
ary[elem == :path ? 0 : 1]
end
- def prep_request(type, info = nil)
- [type_info(type, :path), force_case(info)]
+ def prep_request(type, info = nil)
+ [type_info(type, :path), force_case(info)]
end
public
- # the auth_header parameter refers to a string that can be used in an
- # authorization header. For oauth with jwt tokens this would be something
- # like "bearer xxxx.xxxx.xxxx". The Token class returned by TokenIssuer
- # provides an auth_header method for this purpose.
+ # The +auth_header+ parameter refers to a string that can be used in an
+ # authorization header. For OAuth2 with JWT tokens this would be something
+ # like "bearer xxxx.xxxx.xxxx". The Token class provides
+ # CF::UAA::Token#auth_header for this purpose.
def initialize(target, auth_header) @target, @auth_header = target, auth_header end
- # info is a hash structure converted to json and sent to the scim /Users endpoint
+ # creates a SCIM resource. For possible values for the +type+ parameter, and links
+ # to the schema of each type see #query
+ # info is a hash structure converted to json and sent to the scim endpoint
+ # A hash of the newly created object is returned, including its ID
+ # and meta data.
def add(type, info)
path, info = prep_request(type, info)
reply = json_parse_reply(*json_post(@target, path, info, @auth_header), :down)
# hide client endpoints that are not scim compatible
@@ -84,38 +91,49 @@
return reply if reply && reply["id"]
raise BadResponse, "no id returned by add request to #{@target}#{path}"
end
- def delete(type, id)
+ # Deletes a SCIM resource identified by +id+. For possible values for type, see #query
+ def delete(type, id)
path, _ = prep_request(type)
http_delete @target, "#{path}/#{URI.encode(id)}", @auth_header
end
- # info is a hash structure converted to json and sent to the scim /Users endpoint
+ # +info+ is a hash structure converted to json and sent to a scim endpoint
+ # For possible types, see #query
def put(type, info)
path, info = prep_request(type, info)
ida = type == :client ? 'client_id' : 'id'
raise ArgumentError, "scim info must include #{ida}" unless id = info[ida]
- hdrs = info && info["meta"] && info["meta"]["version"] ?
+ hdrs = info && info["meta"] && info["meta"]["version"] ?
{'if-match' => info["meta"]["version"]} : {}
- reply = json_parse_reply(*json_put(@target, "#{path}/#{URI.encode(id)}",
+ reply = json_parse_reply(*json_put(@target, "#{path}/#{URI.encode(id)}",
info, @auth_header, hdrs), :down)
# hide client endpoints that are not scim compatible
type == :client && !reply ? get(type, info["client_id"]): reply
end
- # TODO: fix this when the UAA supports patch
- # info is a hash structure converted to json and sent to the scim /Users endpoint
- #def patch(path, id, info, attributes_to_delete = nil)
- # info = info.merge(meta: { attributes: Util.arglist(attributes_to_delete) }) if attributes_to_delete
- # json_parse_reply(*json_patch(@target, "#{path}/#{URI.encode(id)}", info, @auth_header))
- #end
-
- # supported query keys are: attributes, filter, startIndex, count
- # output hash keys are: resources, totalResults, itemsPerPage
+ # Queries for objects and returns a selected list of attributes for each
+ # a given filter. Possible values for +type+ and links to the schema of
+ # corresponding object type are:
+ # +:user+:: http://www.simplecloud.info/specs/draft-scim-core-schema-01.html#user-resource
+ # :: http://www.simplecloud.info/specs/draft-scim-core-schema-01.html#anchor8
+ # +:group+:: http://www.simplecloud.info/specs/draft-scim-core-schema-01.html#group-resource
+ # :: http://www.simplecloud.info/specs/draft-scim-core-schema-01.html#anchor10
+ # +:client+::
+ # +:user_id+:: https://github.com/cloudfoundry/uaa/blob/master/docs/UAA-APIs.rst#converting-userids-to-names
+ #
+ # The +query+ hash may contain the following keys:
+ # attributes:: a comma or space separated list of attribute names to be
+ # returned for each object that matches the filter. If no attribute
+ # list is given, all attributes are returned.
+ # filter:: a filter to select which objects are returned. See
+ # http://www.simplecloud.info/specs/draft-scim-api-01.html#query-resources
+ # startIndex:: for paged output, start index of requested result set.
+ # count:: maximum number of results per reply
def query(type, query = {})
path, query = prep_request(type, query)
query = query.reject {|k, v| v.nil? }
if attrs = query['attributes']
attrs = Util.arglist(attrs).map {|a| force_attr(a)}
@@ -131,21 +149,24 @@
raise BadResponse, "invalid reply to query of #{@target}#{path}"
end
info
end
- def get(type, id)
+ # Returns a hash of information about a specific object.
+ # [type] For possible values of type, see #add
+ # [id] the id attribute of the object assigned by the UAA
+ def get(type, id)
path, _ = prep_request(type)
info = json_get(@target, "#{path}/#{URI.encode(id)}", @auth_header, :down)
# hide client endpoints that are not scim compatible
info["id"] = info["client_id"] if type == :client && !info["id"]
info
end
# Collects all pages of entries from a query, returns array of results.
- # Type can be any scim resource type
+ # For descriptions of the +type+ and +query+ parameters, see #query.
def all_pages(type, query = {})
query = query.reject {|k, v| v.nil? }
query["startindex"], info = 1, []
while true
qinfo = query(type, query)
@@ -158,20 +179,20 @@
end
query["startindex"] = info.length + 1
end
end
- # Queries for objects by name. returns array of name/id hashes for each
- # name found.
+ # Queries for objects by name. Returns array of name/id hashes for each
+ # name found. For possible values of +type+, see #query
def ids(type, *names)
na = type_info(type, :name_attr)
filter = names.each_with_object([]) { |n, o| o << "#{na} eq \"#{n}\""}
all_pages(type, attributes: "id,#{na}", filter: filter.join(" or "))
end
- # Convenience method to query for single object by name.
- # Returns its id. Raises error if not found.
+ # Convenience method to query for single object by name. Returns its id.
+ # Raises error if not found. For possible values of +type+, see #query
def id(type, name)
res = ids(type, name)
# hide client endpoints that are not scim compatible
if type == :client && res && res.length > 0
@@ -186,15 +207,29 @@
raise NotFound, "#{name} not found in #{@target}#{type_info(type, :path)}"
end
id
end
+ # [For a user to change their own password] Token must contain "password.write" scope and the
+ # correct +old_password+ must be given.
+ # [For an admin to set a user's password] Token must contain "uaa.admin" scope.
+ #
+ # For more information see:
+ # https://github.com/cloudfoundry/uaa/blob/master/docs/UAA-APIs.rst#change-password-put-useridpassword
+ # or https://github.com/cloudfoundry/uaa/blob/master/docs/UAA-Security.md#password-change
def change_password(user_id, new_password, old_password = nil)
password_request = {"password" => new_password}
password_request["oldPassword"] = old_password if old_password
json_parse_reply(*json_put(@target, "/Users/#{URI.encode(user_id)}/password", password_request, @auth_header))
end
+ # [For a client to change its own secret] Token must contain "uaa.admin,client.secret" scope and the
+ # correct +old_secret+ must be given.
+ # [For an admin to set a client secret] Token must contain "uaa.admin" scope.
+ #
+ # For more information see:
+ # https://github.com/cloudfoundry/uaa/blob/master/docs/UAA-APIs.rst#change-client-secret-put-oauthclientsclient_idsecret
+ # or https://github.com/cloudfoundry/uaa/blob/master/docs/UAA-Security.md#client-secret-mangagement
def change_secret(client_id, new_secret, old_secret = nil)
req = {"secret" => new_secret }
req["oldSecret"] = old_secret if old_secret
json_parse_reply(*json_put(@target, "/oauth/clients/#{URI.encode(client_id)}/secret", req, @auth_header))
end