cloudinary 1.17.0 → 1.20.0

Sign up to get free protection for your applications and to get access to all the features.
checksums.yaml CHANGED
@@ -1,7 +1,7 @@
1
1
  ---
2
2
  SHA256:
3
- metadata.gz: e36bd9efd91c46b00fc0f52a3448ee076990a85b88a470e2cc8e7ccc610ed368
4
- data.tar.gz: dbf3cb94bc5cae2b502b2ca1bea558c93c11e0f77d91fe0577e019452c2acc93
3
+ metadata.gz: 9b641514503d45c3a6c4e8ba5ed87f0f5b8e521260eda789212543be5ccf77a1
4
+ data.tar.gz: 0f65e99aa08e446f01d783af5284a1265aaa4f28a1e95c284addd30beb36ec8c
5
5
  SHA512:
6
- metadata.gz: ba800e50f02feb8cd582a4ca42f6bdfe0fd77c6e36c9dd7e0c3314c9d944bafc451329c79276b02b417f04357aad5ff0ad71e32d3b433f36f2732bfbba70fa84
7
- data.tar.gz: de478592b7552f4aa1b6459effe907b692d22b334b63eb1dc02e7a3ca5e0e5779c7fe82306d3211935df1964f085fdba0e3e6f159716a0055dc45e370be8f6c4
6
+ metadata.gz: 191f627013b5f97fca44e748fd1c4631f9af58a69680b02fdc6b4604e2ddf31216542fcb88e2d1daa825c0ff22a138f2c7d0b41d4895a69cc1b7eaa42a1ab16a
7
+ data.tar.gz: 8a5abe09f562e188e1e6d43d05f462939c62c4070ac50e93c3acaf876998e651aa2fc50775a10c5fb324d388fac9d599c5fe0e60480cdbbebeb7eedf699057e8
data/CHANGELOG.md CHANGED
@@ -1,3 +1,64 @@
1
+ 1.20.0 / 2021-03-26
2
+ ==================
3
+
4
+ New functionality and features
5
+ ------------------------------
6
+
7
+ * Add support for `download_backedup_asset` helper method
8
+ * Add support for `filename_override` upload parameter
9
+ * Add support for `SHA-256` algorithm in auth signatures
10
+
11
+ Other Changes
12
+ -------------
13
+
14
+ * Fix `type` parameter support in ActiveStorage service
15
+ * Fix expression normalization in advanced cases
16
+ * Add test for context metadata as user variables
17
+ * Improve validation of auth token generation
18
+
19
+
20
+ 1.19.0 / 2021-03-05
21
+ ==================
22
+
23
+ New functionality and features
24
+ ------------------------------
25
+
26
+ * Add Account Provisioning API
27
+ * Add support for `api_proxy` parameter
28
+ * Add support for `date` parameter in `usage` Admin API
29
+
30
+ Other Changes
31
+ -------------
32
+
33
+ * Fix direct upload of raw files
34
+ * Improve unit testing of add-ons
35
+ * Change test for `eval` upload parameter
36
+ * Bump vulnerable version of rubyzip
37
+ * Fix `cloudinary.gemspec` glob issue
38
+
39
+ 1.18.1 / 2020-09-30
40
+ ===================
41
+
42
+ * Update embedded `jquery.cloudinary.js` to fix ES5 compatibility issue
43
+
44
+ 1.18.0 / 2020-09-27
45
+ ===================
46
+
47
+ New functionality and features
48
+ ------------------------------
49
+ * Add `download_folder` helper
50
+ * Add support for `sources` in `video` tag
51
+ * Add structured metadata to Admin and Upload API
52
+
53
+ Other Changes
54
+ -------------
55
+ * Fix download of a raw file in ActiveStorage
56
+ * Update embedded `jquery.cloudinary.js` to fix ES5 compatibility issue
57
+
58
+ 1.17.1 / 2020-08-25
59
+ ===================
60
+
61
+ * Fix options handling issue in SassC
1
62
 
2
63
  1.17.0 / 2020-08-21
3
64
  ===================
data/cloudinary.gemspec CHANGED
@@ -15,12 +15,17 @@ Gem::Specification.new do |s|
15
15
 
16
16
  s.rubyforge_project = "cloudinary"
17
17
 
18
- s.files = (`git ls-files`.split("\n") - `git ls-files {test,spec,features,samples}/*`.split("\n")) + Dir.glob("vendor/assets/javascripts/*/*") + Dir.glob("vendor/assets/html/*")
18
+ s.files = `git ls-files`.split("\n").select { |f| !f.start_with?("test", "spec", "features", "samples") } + Dir.glob("vendor/assets/javascripts/*/*") + Dir.glob("vendor/assets/html/*")
19
19
  s.executables = `git ls-files -- bin/*`.split("\n").map{ |f| File.basename(f) }
20
20
  s.require_paths = ["lib"]
21
21
 
22
22
  s.add_dependency "aws_cf_signer"
23
- s.add_dependency "rest-client"
23
+
24
+ if RUBY_VERSION >= "2.0.0"
25
+ s.add_dependency "rest-client", ">= 2.0.0"
26
+ else
27
+ s.add_dependency "rest-client"
28
+ end
24
29
 
25
30
  s.add_development_dependency "actionpack"
26
31
  s.add_development_dependency "nokogiri"
@@ -39,7 +44,7 @@ Gem::Specification.new do |s|
39
44
  s.add_development_dependency "railties", "<= 4.2.7" if RUBY_VERSION <= "1.9.3"
40
45
  s.add_development_dependency "rspec-rails"
41
46
 
42
- s.add_development_dependency "rubyzip", "<=1.2.0" # support testing Ruby 1.9
47
+ s.add_development_dependency "rubyzip"
43
48
 
44
49
  if RUBY_VERSION <= "2.4.0"
45
50
  s.add_development_dependency "simplecov", "<= 0.17.1" # support testing Ruby 1.9
@@ -1,5 +1,6 @@
1
1
  require 'active_storage/blob_key'
2
2
  require 'cloudinary/helper'
3
+ require 'net/http'
3
4
 
4
5
  unless ActiveStorage::Blob.method_defined? :original_key
5
6
  class ActiveStorage::Blob
@@ -57,7 +58,7 @@ module ActiveStorage
57
58
  url = Cloudinary::Utils.cloudinary_url(
58
59
  public_id(key),
59
60
  resource_type: resource_type(nil, key),
60
- format: ext_for_file(filename, content_type),
61
+ format: ext_for_file(key, filename, content_type),
61
62
  **@options.merge(options.symbolize_keys)
62
63
  )
63
64
 
@@ -71,6 +72,14 @@ module ActiveStorage
71
72
  instrument :url, key: key do |payload|
72
73
  options = {:resource_type => resource_type(nil, key)}.merge(@options.merge(options.symbolize_keys))
73
74
  options[:public_id] = public_id_internal(key)
75
+ # Provide file format for raw files, since js client does not include original file name.
76
+ #
77
+ # When the file is uploaded from the server, the request includes original filename. That allows Cloudinary
78
+ # to identify file extension and append it to the public id of the file (raw files include file extension
79
+ # in their public id, opposed to transformable assets (images/video) that use only basename). When uploading
80
+ # through direct upload (client side js), filename is missing, and that leads to inconsistent/broken URLs.
81
+ # To avoid that, we explicitly pass file format in options.
82
+ options[:format] = ext_for_file(key) if options[:resource_type] == "raw"
74
83
  options[:context] = {active_storage_key: key}
75
84
  options.delete(:file)
76
85
  payload[:url] = api_uri("upload", options)
@@ -86,7 +95,12 @@ module ActiveStorage
86
95
 
87
96
  def delete(key)
88
97
  instrument :delete, key: key do
89
- Cloudinary::Uploader.destroy public_id(key), resource_type: resource_type(nil, key)
98
+ options = {
99
+ resource_type: resource_type(nil, key),
100
+ type: @options[:type]
101
+ }.compact
102
+
103
+ Cloudinary::Uploader.destroy public_id(key), **options
90
104
  end
91
105
  end
92
106
 
@@ -98,7 +112,12 @@ module ActiveStorage
98
112
  def exist?(key)
99
113
  instrument :exist, key: key do |payload|
100
114
  begin
101
- Cloudinary::Api.resource public_id(key), resource_type: resource_type(nil, key)
115
+ options = {
116
+ resource_type: resource_type(nil, key),
117
+ type: @options[:type]
118
+ }.compact
119
+
120
+ Cloudinary::Api.resource public_id(key), **options
102
121
  true
103
122
  rescue Cloudinary::Api::NotFound => e
104
123
  false
@@ -107,8 +126,7 @@ module ActiveStorage
107
126
  end
108
127
 
109
128
  def download(key, &block)
110
- url = Cloudinary::Utils.unsigned_download_url(public_id(key), resource_type: resource_type(nil, key))
111
- uri = URI(url)
129
+ uri = URI(url(key))
112
130
  if block_given?
113
131
  instrument :streaming_download, key: key do
114
132
  Net::HTTP.start(uri.host, uri.port, use_ssl: uri.scheme == 'https') do |http|
@@ -169,12 +187,18 @@ module ActiveStorage
169
187
  #
170
188
  # It does the best effort when original filename does not include extension, but we know the mime-type.
171
189
  #
190
+ # @param [ActiveStorage::BlobKey] key The blob key with attributes.
172
191
  # @param [ActiveStorage::Filename] filename The original filename.
173
192
  # @param [string] content_type The content type of the file.
174
193
  #
175
194
  # @return [string] The extension of the filename.
176
- def ext_for_file(filename, content_type)
195
+ def ext_for_file(key, filename = nil, content_type = nil)
196
+ if filename.blank?
197
+ options = key.respond_to?(:attributes) ? key.attributes : {}
198
+ filename = ActiveStorage::Filename.new(options[:filename]) if options.has_key?(:filename)
199
+ end
177
200
  ext = filename.respond_to?(:extension_without_delimiter) ? filename.extension_without_delimiter : nil
201
+
178
202
  return ext unless ext.blank?
179
203
 
180
204
  # Raw files are not convertible, no extension guessing for them
@@ -194,6 +218,8 @@ module ActiveStorage
194
218
  end
195
219
 
196
220
  def content_type_to_resource_type(content_type)
221
+ return 'image' if content_type.nil?
222
+
197
223
  type, subtype = content_type.split('/')
198
224
  case type
199
225
  when 'video', 'audio'
data/lib/cloudinary.rb CHANGED
@@ -16,7 +16,12 @@ require "cloudinary/missing"
16
16
  module Cloudinary
17
17
  autoload :Utils, 'cloudinary/utils'
18
18
  autoload :Uploader, 'cloudinary/uploader'
19
+ autoload :BaseConfig, "cloudinary/base_config"
20
+ autoload :Config, "cloudinary/config"
21
+ autoload :AccountConfig, "cloudinary/account_config"
22
+ autoload :BaseApi, "cloudinary/base_api"
19
23
  autoload :Api, "cloudinary/api"
24
+ autoload :AccountApi, "cloudinary/account_api"
20
25
  autoload :Downloader, "cloudinary/downloader"
21
26
  autoload :Blob, "cloudinary/blob"
22
27
  autoload :PreloadedFile, "cloudinary/preloaded_file"
@@ -58,64 +63,42 @@ module Cloudinary
58
63
  "ept" => "eps"
59
64
  }
60
65
 
61
- @@config = nil
62
-
66
+ # Cloudinary config
67
+ #
68
+ # @param [Hash] new_config If +new_config+ is passed, Config will be updated with it
69
+ # @yieldparam [OpenStruct] Config can be updated in the block
70
+ #
71
+ # @return [OpenStruct]
63
72
  def self.config(new_config=nil)
64
- first_time = @@config.nil?
65
- @@config ||= OpenStruct.new((YAML.load(ERB.new(IO.read(config_dir.join("cloudinary.yml"))).result)[config_env] rescue {}))
66
-
67
- config_from_env if first_time
73
+ @@config ||= make_new_config(Config)
68
74
 
69
- set_config(new_config) if new_config
70
- yield(@@config) if block_given?
75
+ @@config.update(new_config) if new_config
76
+ yield @@config if block_given?
71
77
 
72
78
  @@config
73
79
  end
74
80
 
75
- def self.config_from_url(url)
76
- @@config ||= OpenStruct.new
77
- return unless url && !url.empty?
78
- uri = URI.parse(url)
79
- if !uri.scheme || "cloudinary" != uri.scheme.downcase
80
- raise(CloudinaryException,
81
- "Invalid CLOUDINARY_URL scheme. Expecting to start with 'cloudinary://'")
82
- end
83
- set_config(
84
- "cloud_name" => uri.host,
85
- "api_key" => uri.user,
86
- "api_secret" => uri.password,
87
- "private_cdn" => !uri.path.blank?,
88
- "secure_distribution" => uri.path[1..-1]
89
- )
90
- uri.query.to_s.split("&").each do
91
- |param|
92
- key, value = param.split("=")
93
- if isNestedKey? key
94
- putNestedKey key, value
95
- else
96
- set_config(key => Utils.smart_unescape(value))
97
- end
98
- end
99
- end
81
+ # Cloudinary account config
82
+ #
83
+ # @param [Hash] new_config If +new_config+ is passed, Account Config will be updated with it
84
+ # @yieldparam [OpenStruct] Account config can be updated in the block
85
+ #
86
+ # @return [OpenStruct]
87
+ def self.account_config(new_config=nil)
88
+ @@account_config ||= make_new_config(AccountConfig)
100
89
 
101
- def self.putNestedKey(key, value)
102
- chain = key.split(/[\[\]]+/).reject { |i| i.empty? }
103
- outer = @@config
104
- lastKey = chain.pop()
105
- chain.each do |innerKey|
106
- inner = outer[innerKey]
107
- if inner.nil?
108
- inner = OpenStruct.new
109
- outer[innerKey] = inner
110
- end
111
- outer = inner
112
- end
113
- outer[lastKey] = value
90
+ @@account_config.update(new_config) if new_config
91
+ yield @@account_config if block_given?
92
+
93
+ @@account_config
114
94
  end
115
95
 
96
+ def self.config_from_url(url)
97
+ config.load_from_url(url)
98
+ end
116
99
 
117
- def self.isNestedKey?(key)
118
- /\w+\[\w+\]/ =~ key
100
+ def self.config_from_account_url(url)
101
+ account_config.load_from_url(url)
119
102
  end
120
103
 
121
104
  def self.app_root
@@ -129,22 +112,6 @@ module Cloudinary
129
112
 
130
113
  private
131
114
 
132
- def self.config_from_env
133
- # Heroku support
134
- if ENV["CLOUDINARY_CLOUD_NAME"]
135
- config_keys = ENV.keys.select! { |key| key.start_with? "CLOUDINARY_" }
136
- config_keys -= ["CLOUDINARY_URL"] # ignore it when explicit options are passed
137
- config_keys.each do |full_key|
138
- conf_key = full_key["CLOUDINARY_".length..-1].downcase # convert "CLOUDINARY_CONFIG_NAME" to "config_name"
139
- conf_val = ENV[full_key]
140
- conf_val = conf_val == 'true' if %w[true false].include?(conf_val) # cast relevant boolean values
141
- set_config(conf_key => conf_val)
142
- end
143
- elsif ENV["CLOUDINARY_URL"]
144
- config_from_url(ENV["CLOUDINARY_URL"])
145
- end
146
- end
147
-
148
115
  def self.config_env
149
116
  return ENV["CLOUDINARY_ENV"] if ENV["CLOUDINARY_ENV"]
150
117
  return Rails.env if defined? Rails::env
@@ -159,6 +126,20 @@ module Cloudinary
159
126
  def self.set_config(new_config)
160
127
  new_config.each{|k,v| @@config.send(:"#{k}=", v) if !v.nil?}
161
128
  end
129
+
130
+ # Builds config from yaml file, extends it with specific module and loads configuration from environment variable
131
+ #
132
+ # @param [Module] config_module Config is extended with this module after being built
133
+ #
134
+ # @return [OpenStruct]
135
+ def self.make_new_config(config_module)
136
+ OpenStruct.new((YAML.load(ERB.new(IO.read(config_dir.join("cloudinary.yml"))).result)[config_env] rescue {})).tap do |config|
137
+ config.extend(config_module)
138
+ config.load_config_from_env
139
+ end
140
+ end
141
+
142
+ private_class_method :make_new_config
162
143
  end
163
144
  # Prevent require loop if included after Rails is already initialized.
164
145
  require "cloudinary/helper" if defined?(::ActionView::Base)
@@ -0,0 +1,231 @@
1
+ class Cloudinary::AccountApi
2
+ extend Cloudinary::BaseApi
3
+
4
+ # Creates a new sub-account. Any users that have access to all sub-accounts will also automatically have access to the
5
+ # new sub-account.
6
+ # @param [String] name The display name as shown in the management console
7
+ # @param [String] cloud_name A case-insensitive cloud name comprised of alphanumeric and underscore characters.
8
+ # Generates an error if the specified cloud name is not unique across all Cloudinary accounts.
9
+ # Note: Once created, the name can only be changed for accounts with fewer than 1000 assets.
10
+ # @param [Object] custom_attributes Any custom attributes you want to associate with the sub-account
11
+ # @param [Boolean] enabled Whether to create the account as enabled (default is enabled)
12
+ # @param [String] base_account ID of sub-account from which to copy settings
13
+ # @param [Object] options additional options
14
+ def self.create_sub_account(name, cloud_name = nil, custom_attributes = {}, enabled = nil, base_account = nil, options = {})
15
+ params = {
16
+ name: name,
17
+ cloud_name: cloud_name,
18
+ custom_attributes: custom_attributes,
19
+ enabled: enabled,
20
+ base_sub_account_id: base_account
21
+ }
22
+
23
+ call_account_api(:post, 'sub_accounts', params, options.merge(content_type: :json))
24
+ end
25
+
26
+ # Updates the specified details of the sub-account.
27
+ # @param [String] sub_account_id The ID of the sub-account.
28
+ # @param [String] name The display name as shown in the management console
29
+ # @param [String] cloud_name A case-insensitive cloud name comprised of alphanumeric and underscore characters.
30
+ # Generates an error if the specified cloud name is not unique across all Cloudinary accounts.
31
+ # Note: Once created, the name can only be changed for accounts with fewer than 1000 assets.
32
+ # @param [Object] custom_attributes Any custom attributes you want to associate with the sub-account, as a map/hash
33
+ # of key/value pairs.
34
+ # @param [Boolean] enabled Whether the sub-account is enabled.
35
+ # @param [Object] options additional options
36
+ def self.update_sub_account(sub_account_id, name = nil, cloud_name = nil, custom_attributes = nil, enabled = nil, options = {})
37
+ params = {
38
+ name: name,
39
+ cloud_name: cloud_name,
40
+ custom_attributes: custom_attributes,
41
+ enabled: enabled
42
+ }
43
+
44
+ call_account_api(:put, ['sub_accounts', sub_account_id], params, options.merge(content_type: :json))
45
+ end
46
+
47
+ # Lists sub-accounts.
48
+ # @param [Boolean] enabled Whether to only return enabled sub-accounts (true) or disabled accounts (false).
49
+ # Default: all accounts are returned (both enabled and disabled).
50
+ # @param [Array<String>] ids A list of up to 100 sub-account IDs. When provided, other parameters are ignored.
51
+ # @param [String] prefix Returns accounts where the name begins with the specified case-insensitive string.
52
+ # @param [Object] options additional options
53
+ def self.sub_accounts(enabled = nil, ids = [], prefix = nil, options = {})
54
+ params = {
55
+ enabled: enabled,
56
+ ids: ids,
57
+ prefix: prefix
58
+ }
59
+
60
+ call_account_api(:get, 'sub_accounts', params, options.merge(content_type: :json))
61
+ end
62
+
63
+ # Retrieves the details of the specified sub-account.
64
+ # @param [String] sub_account_id The ID of the sub-account.
65
+ # @param [Object] options additional options
66
+ def self.sub_account(sub_account_id, options = {})
67
+ call_account_api(:get, ['sub_accounts', sub_account_id], {}, options.merge(content_type: :json))
68
+ end
69
+
70
+ # Deletes the specified sub-account. Supported only for accounts with fewer than 1000 assets.
71
+ # @param [String] sub_account_id The ID of the sub-account.
72
+ # @param [Object] options additional options
73
+ def self.delete_sub_account(sub_account_id, options = {})
74
+ call_account_api(:delete, ['sub_accounts', sub_account_id], {}, options)
75
+ end
76
+
77
+ # Creates a new user in the account.
78
+ # @param [String] name The name of the user.
79
+ # @param [String] email A unique email address, which serves as the login name and notification address.
80
+ # @param [String] role The role to assign. Possible values: master_admin, admin, billing, technical_admin, reports,
81
+ # media_library_admin, media_library_user
82
+ # @param [Array<String>] sub_account_ids The list of sub-account IDs that this user can access.
83
+ # Note: This parameter is ignored if the role is specified as master_admin.
84
+ # @param [Object] options additional options
85
+ def self.create_user(name, email, role, sub_account_ids = [], options = {})
86
+ params = {
87
+ name: name,
88
+ email: email,
89
+ role: role,
90
+ sub_account_ids: sub_account_ids
91
+ }
92
+
93
+ call_account_api(:post, 'users', params, options.merge(content_type: :json))
94
+ end
95
+
96
+ # Deletes an existing user.
97
+ # @param [String] user_id The ID of the user to delete.
98
+ # @param [Object] options additional options
99
+ def self.delete_user(user_id, options = {})
100
+ call_account_api(:delete, ['users', user_id], {}, options)
101
+ end
102
+
103
+ # Updates the details of the specified user.
104
+ # @param [String] user_id The ID of the user to update.
105
+ # @param [String] name The name of the user.
106
+ # @param [String] email A unique email address, which serves as the login name and notification address.
107
+ # @param [String] role The role to assign. Possible values: master_admin, admin, billing, technical_admin, reports,
108
+ # media_library_admin, media_library_user
109
+ # @param [Array<String>] sub_account_ids The list of sub-account IDs that this user can access.
110
+ # Note: This parameter is ignored if the role is specified as master_admin.
111
+ # @param [Object] options additional options
112
+ def self.update_user(user_id, name = nil, email = nil, role = nil, sub_account_ids = nil, options = {})
113
+ params = {
114
+ name: name,
115
+ email: email,
116
+ role: role,
117
+ sub_account_ids: sub_account_ids
118
+ }
119
+
120
+ call_account_api(:put, ['users', user_id], params, options.merge(content_type: :json))
121
+ end
122
+
123
+ # Returns the user with the specified ID.
124
+ # @param [String] user_id The ID of the user.
125
+ # @param [Object] options additional options
126
+ def self.user(user_id, options = {})
127
+ call_account_api(:get, ['users', user_id], {}, options.merge(content_type: :json))
128
+ end
129
+
130
+ # Lists users in the account.
131
+ # @param [Boolean] pending Whether to only return pending users. Default: all users
132
+ # @param [Array<String>] user_ids A list of up to 100 user IDs. When provided, other parameters are ignored.
133
+ # @param [String] prefix Returns users where the name or email address begins with the specified case-insensitive string.
134
+ # @param [String] sub_account_id Only returns users who have access to the specified account.
135
+ # @param [Object] options additional options
136
+ def self.users(pending = nil, user_ids = [], prefix = nil, sub_account_id = nil, options = {})
137
+ params = if user_ids && user_ids.count > 0
138
+ {
139
+ ids: user_ids
140
+ }
141
+ else
142
+ {
143
+ prefix: prefix,
144
+ sub_account_id: sub_account_id,
145
+ pending: pending
146
+ }
147
+ end
148
+
149
+ call_account_api(:get, 'users', params, options.merge(content_type: :json))
150
+ end
151
+
152
+ # Creates a new user group.
153
+ # @param [String] name The name for the user group.
154
+ # @param [Object] options additional options
155
+ def self.create_user_group(name, options = {})
156
+ params = {
157
+ name: name
158
+ }
159
+
160
+ call_account_api(:post, 'user_groups', params, options.merge(content_type: :json))
161
+ end
162
+
163
+ # Updates the specified user group.
164
+ # @param [String] group_id The ID of the user group to update.
165
+ # @param [String] name The name for the user group.
166
+ # @param [Object] options additional options
167
+ def self.update_user_group(group_id, name, options = {})
168
+ params = {
169
+ name: name
170
+ }
171
+
172
+ call_account_api(:put, ['user_groups', group_id], params, options.merge(content_type: :json))
173
+ end
174
+
175
+ # Adds a user to a group with the specified ID.
176
+ # @param [String] group_id The ID of the user group.
177
+ # @param [String] user_id The ID of the user.
178
+ # @param [Object] options additional options
179
+ def self.add_user_to_group(group_id, user_id, options = {})
180
+ call_account_api(:post, ['user_groups', group_id, 'users', user_id], {}, options.merge(content_type: :json))
181
+ end
182
+
183
+ # Removes a user from a group with the specified ID.
184
+ # @param [String] group_id The ID of the user group.
185
+ # @param [String] user_id The ID of the user.
186
+ # @param [Object] options additional options
187
+ def self.remove_user_from_group(group_id, user_id, options = {})
188
+ call_account_api(:delete, ['user_groups', group_id, 'users', user_id], {}, options.merge(content_type: :json))
189
+ end
190
+
191
+ # Deletes the user group with the specified ID.
192
+ # @param [String] group_id The ID of the user group to delete.
193
+ # @param [Object] options additional options
194
+ def self.delete_user_group(group_id, options = {})
195
+ call_account_api(:delete, ['user_groups', group_id], {}, options)
196
+ end
197
+
198
+ # Lists user groups in the account.
199
+ # @param [Object] options additional options
200
+ def self.user_groups(options = {})
201
+ call_account_api(:get, 'user_groups', {}, options.merge(content_type: :json))
202
+ end
203
+
204
+ # Retrieves the details of the specified user group.
205
+ # @param [String] group_id The ID of the user group to retrieve.
206
+ # @param [Object] options additional options
207
+ def self.user_group(group_id, options = {})
208
+ call_account_api(:get, ['user_groups', group_id], {}, options.merge(content_type: :json))
209
+ end
210
+
211
+ # Lists users in the specified user group.
212
+ # @param [String] group_id The ID of the user group.
213
+ # @param [Object] options additional options
214
+ def self.user_group_users(group_id, options = {})
215
+ call_account_api(:get, ['user_groups', group_id, 'users'], {}, options.merge(content_type: :json))
216
+ end
217
+
218
+ def self.call_account_api(method, uri, params, options)
219
+ account_id = options[:account_id] || Cloudinary.account_config.account_id || raise('Must supply account_id')
220
+ api_key = options[:provisioning_api_key] || Cloudinary.account_config.provisioning_api_key || raise('Must supply provisioning api_key')
221
+ api_secret = options[:provisioning_api_secret] || Cloudinary.account_config.provisioning_api_secret || raise('Must supply provisioning api_secret')
222
+
223
+ params.reject! { |_, v| v.nil? }
224
+
225
+ call_cloudinary_api(method, uri, api_key, api_secret, params, options) do |cloudinary, inner_uri|
226
+ [cloudinary, 'v1_1', 'provisioning', 'accounts', account_id, inner_uri]
227
+ end
228
+ end
229
+
230
+ private_class_method :call_account_api
231
+ end