ncfoundry 4.9.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.
Files changed (268) hide show
  1. checksums.yaml +7 -0
  2. data/.gitignore +8 -0
  3. data/.gitmodules +3 -0
  4. data/.rspec +2 -0
  5. data/.ruby-gemset +1 -0
  6. data/.ruby-version +1 -0
  7. data/.travis.yml +11 -0
  8. data/Checkfile +2 -0
  9. data/Gemfile +3 -0
  10. data/LICENSE +987 -0
  11. data/NOTICE +10 -0
  12. data/README.md +9 -0
  13. data/Rakefile +14 -0
  14. data/VERSION +1 -0
  15. data/cfoundry.gemspec +41 -0
  16. data/config/locales/en.yml +168 -0
  17. data/lib/cc_api_stub/app_usage_events.rb +11 -0
  18. data/lib/cc_api_stub/applications.rb +57 -0
  19. data/lib/cc_api_stub/domains.rb +32 -0
  20. data/lib/cc_api_stub/events.rb +11 -0
  21. data/lib/cc_api_stub/frameworks.rb +22 -0
  22. data/lib/cc_api_stub/helper.rb +148 -0
  23. data/lib/cc_api_stub/login.rb +21 -0
  24. data/lib/cc_api_stub/organization_users.rb +27 -0
  25. data/lib/cc_api_stub/organizations.rb +82 -0
  26. data/lib/cc_api_stub/routes.rb +26 -0
  27. data/lib/cc_api_stub/runtimes.rb +22 -0
  28. data/lib/cc_api_stub/service_bindings.rb +22 -0
  29. data/lib/cc_api_stub/service_instances.rb +22 -0
  30. data/lib/cc_api_stub/services.rb +21 -0
  31. data/lib/cc_api_stub/space_users.rb +27 -0
  32. data/lib/cc_api_stub/spaces.rb +58 -0
  33. data/lib/cc_api_stub/users.rb +85 -0
  34. data/lib/cc_api_stub.rb +20 -0
  35. data/lib/cfoundry/auth_token.rb +62 -0
  36. data/lib/cfoundry/baseclient.rb +220 -0
  37. data/lib/cfoundry/chatty_hash.rb +46 -0
  38. data/lib/cfoundry/client.rb +42 -0
  39. data/lib/cfoundry/concerns/login_helpers.rb +14 -0
  40. data/lib/cfoundry/concerns/proxy_options.rb +14 -0
  41. data/lib/cfoundry/errors.rb +163 -0
  42. data/lib/cfoundry/rest_client.rb +316 -0
  43. data/lib/cfoundry/test_support.rb +3 -0
  44. data/lib/cfoundry/trace_helpers.rb +64 -0
  45. data/lib/cfoundry/uaaclient.rb +151 -0
  46. data/lib/cfoundry/upload_helpers.rb +222 -0
  47. data/lib/cfoundry/v2/app.rb +313 -0
  48. data/lib/cfoundry/v2/app_event.rb +13 -0
  49. data/lib/cfoundry/v2/app_instance.rb +74 -0
  50. data/lib/cfoundry/v2/app_usage_event.rb +14 -0
  51. data/lib/cfoundry/v2/base.rb +112 -0
  52. data/lib/cfoundry/v2/client.rb +104 -0
  53. data/lib/cfoundry/v2/domain.rb +20 -0
  54. data/lib/cfoundry/v2/event.rb +17 -0
  55. data/lib/cfoundry/v2/helper.rb +11 -0
  56. data/lib/cfoundry/v2/managed_service_instance.rb +13 -0
  57. data/lib/cfoundry/v2/model.rb +213 -0
  58. data/lib/cfoundry/v2/model_magic/attribute.rb +49 -0
  59. data/lib/cfoundry/v2/model_magic/client_extensions.rb +170 -0
  60. data/lib/cfoundry/v2/model_magic/has_summary.rb +49 -0
  61. data/lib/cfoundry/v2/model_magic/query_multi_value_helper.rb +21 -0
  62. data/lib/cfoundry/v2/model_magic/query_value_helper.rb +33 -0
  63. data/lib/cfoundry/v2/model_magic/queryable_by.rb +39 -0
  64. data/lib/cfoundry/v2/model_magic/to_many.rb +145 -0
  65. data/lib/cfoundry/v2/model_magic/to_one.rb +81 -0
  66. data/lib/cfoundry/v2/model_magic.rb +134 -0
  67. data/lib/cfoundry/v2/organization.rb +44 -0
  68. data/lib/cfoundry/v2/quota_definition.rb +12 -0
  69. data/lib/cfoundry/v2/route.rb +25 -0
  70. data/lib/cfoundry/v2/service.rb +22 -0
  71. data/lib/cfoundry/v2/service_auth_token.rb +9 -0
  72. data/lib/cfoundry/v2/service_binding.rb +10 -0
  73. data/lib/cfoundry/v2/service_broker.rb +12 -0
  74. data/lib/cfoundry/v2/service_instance.rb +14 -0
  75. data/lib/cfoundry/v2/service_plan.rb +16 -0
  76. data/lib/cfoundry/v2/space.rb +31 -0
  77. data/lib/cfoundry/v2/stack.rb +10 -0
  78. data/lib/cfoundry/v2/user.rb +97 -0
  79. data/lib/cfoundry/v2/user_provided_service_instance.rb +16 -0
  80. data/lib/cfoundry/validator.rb +41 -0
  81. data/lib/cfoundry/version.rb +4 -0
  82. data/lib/cfoundry/zip.rb +56 -0
  83. data/lib/cfoundry.rb +6 -0
  84. data/release_notes/release_1_5_3.md +177 -0
  85. data/release_notes/release_2.3.1.md +14 -0
  86. data/release_notes/release_2.3.3.md +38 -0
  87. data/release_notes/release_2.3.4.md +16 -0
  88. data/release_notes/release_2.3.5.md +14 -0
  89. data/release_notes/release_2.3.6.md +14 -0
  90. data/release_notes/release_2.3.6.rc1.md +29 -0
  91. data/release_notes/release_2.3.6.rc2.md +19 -0
  92. data/release_notes/release_2.3.7.rc1.md +14 -0
  93. data/release_notes/release_2.4.0.md +41 -0
  94. data/release_notes/release_2.4.1.rc1.md +58 -0
  95. data/release_notes/release_3.0.0.md +21 -0
  96. data/release_notes/release_3.0.1.md +16 -0
  97. data/release_notes/release_3.0.2.rc1.md +19 -0
  98. data/release_notes/release_4.0.0.md +19 -0
  99. data/release_notes/release_4.0.1.md +14 -0
  100. data/release_notes/release_4.0.2.rc1.md +14 -0
  101. data/release_notes/release_4.0.2.rc2.md +11 -0
  102. data/release_notes/release_4.0.2.rc3.md +11 -0
  103. data/release_notes/release_4.0.2.rc4.md +19 -0
  104. data/release_notes/release_4.0.2.rc5.md +14 -0
  105. data/release_notes/release_4.0.3.md +19 -0
  106. data/release_notes/release_4.0.4.rc1.md +14 -0
  107. data/release_notes/release_4.0.4.rc2.md +14 -0
  108. data/release_notes/release_4.1.0.md +19 -0
  109. data/release_notes/release_4.2.0.rc.md +24 -0
  110. data/release_notes/release_4.3.0.md +22 -0
  111. data/release_notes/release_4.3.1.md +14 -0
  112. data/release_notes/release_4.3.10.md +14 -0
  113. data/release_notes/release_4.3.11.md +14 -0
  114. data/release_notes/release_4.3.12.md +14 -0
  115. data/release_notes/release_4.3.2.rc1.md +21 -0
  116. data/release_notes/release_4.3.3.md +24 -0
  117. data/release_notes/release_4.3.4.md +11 -0
  118. data/release_notes/release_4.3.4.rc1.md +24 -0
  119. data/release_notes/release_4.3.5.md +36 -0
  120. data/release_notes/release_4.3.5.rc1.md +21 -0
  121. data/release_notes/release_4.3.6.md +14 -0
  122. data/release_notes/release_4.3.7.md +50 -0
  123. data/release_notes/release_4.3.8.md +19 -0
  124. data/release_notes/release_4.3.9.md +14 -0
  125. data/release_notes/release_4.4.0.md +16 -0
  126. data/release_notes/release_4.5.1.md +11 -0
  127. data/release_notes/release_4.5.2.md +14 -0
  128. data/release_notes/release_4.5.3.md +29 -0
  129. data/release_notes/release_4.6.0.md +24 -0
  130. data/release_notes/release_4.6.1.md +14 -0
  131. data/release_notes/release_4.6.2.md +16 -0
  132. data/release_notes/release_4.6.3.rc1.md +14 -0
  133. data/release_notes/release_4.6.3.rc2.md +14 -0
  134. data/release_notes/release_4.6.3.rc3.md +17 -0
  135. data/release_notes/release_4.7.0.md +11 -0
  136. data/release_notes/release_4.7.1.md +14 -0
  137. data/release_notes/release_4.7.1.rc.1.md +14 -0
  138. data/release_notes/release_4.7.1.rc1.md +19 -0
  139. data/script/gpp +3 -0
  140. data/spec/cc_api_stub/app_usage_events_spec.rb +12 -0
  141. data/spec/cc_api_stub/applications_spec.rb +69 -0
  142. data/spec/cc_api_stub/domains_spec.rb +40 -0
  143. data/spec/cc_api_stub/events_spec.rb +12 -0
  144. data/spec/cc_api_stub/frameworks_spec.rb +19 -0
  145. data/spec/cc_api_stub/login_spec.rb +20 -0
  146. data/spec/cc_api_stub/organization_users_spec.rb +35 -0
  147. data/spec/cc_api_stub/organizations_spec.rb +118 -0
  148. data/spec/cc_api_stub/routes_spec.rb +19 -0
  149. data/spec/cc_api_stub/runtimes_spec.rb +19 -0
  150. data/spec/cc_api_stub/service_bindings_spec.rb +13 -0
  151. data/spec/cc_api_stub/service_instances_spec.rb +19 -0
  152. data/spec/cc_api_stub/services_spec.rb +11 -0
  153. data/spec/cc_api_stub/space_users_spec.rb +35 -0
  154. data/spec/cc_api_stub/spaces_spec.rb +38 -0
  155. data/spec/cc_api_stub/users_spec.rb +107 -0
  156. data/spec/cfoundry/auth_token_spec.rb +153 -0
  157. data/spec/cfoundry/baseclient_spec.rb +284 -0
  158. data/spec/cfoundry/client_spec.rb +13 -0
  159. data/spec/cfoundry/errors_spec.rb +117 -0
  160. data/spec/cfoundry/rest_client_spec.rb +367 -0
  161. data/spec/cfoundry/trace_helpers_spec.rb +91 -0
  162. data/spec/cfoundry/uaaclient_spec.rb +421 -0
  163. data/spec/cfoundry/upload_helpers_spec.rb +182 -0
  164. data/spec/cfoundry/v2/app_event_spec.rb +97 -0
  165. data/spec/cfoundry/v2/app_instance_spec.rb +31 -0
  166. data/spec/cfoundry/v2/app_spec.rb +354 -0
  167. data/spec/cfoundry/v2/app_usage_event_spec.rb +15 -0
  168. data/spec/cfoundry/v2/base_spec.rb +375 -0
  169. data/spec/cfoundry/v2/client_spec.rb +121 -0
  170. data/spec/cfoundry/v2/domain_spec.rb +63 -0
  171. data/spec/cfoundry/v2/event_spec.rb +15 -0
  172. data/spec/cfoundry/v2/managed_service_instance_spec.rb +149 -0
  173. data/spec/cfoundry/v2/model_magic/attribute_spec.rb +123 -0
  174. data/spec/cfoundry/v2/model_magic/has_summary_spec.rb +17 -0
  175. data/spec/cfoundry/v2/model_magic/to_many_spec.rb +53 -0
  176. data/spec/cfoundry/v2/model_magic/to_one_spec.rb +106 -0
  177. data/spec/cfoundry/v2/model_magic_spec.rb +43 -0
  178. data/spec/cfoundry/v2/model_spec.rb +472 -0
  179. data/spec/cfoundry/v2/organization_spec.rb +282 -0
  180. data/spec/cfoundry/v2/quota_definition_spec.rb +50 -0
  181. data/spec/cfoundry/v2/route_spec.rb +42 -0
  182. data/spec/cfoundry/v2/service_plan_spec.rb +53 -0
  183. data/spec/cfoundry/v2/service_spec.rb +58 -0
  184. data/spec/cfoundry/v2/space_spec.rb +160 -0
  185. data/spec/cfoundry/v2/user_provided_service_instance_spec.rb +57 -0
  186. data/spec/cfoundry/v2/user_spec.rb +206 -0
  187. data/spec/cfoundry/validator_spec.rb +94 -0
  188. data/spec/factories/app_events_factory.rb +7 -0
  189. data/spec/factories/app_usage_events_factory.rb +32 -0
  190. data/spec/factories/apps_factory.rb +11 -0
  191. data/spec/factories/clients_factory.rb +7 -0
  192. data/spec/factories/domains_factory.rb +10 -0
  193. data/spec/factories/events_factory.rb +50 -0
  194. data/spec/factories/organizations_factory.rb +12 -0
  195. data/spec/factories/quota_definitions_factory.rb +8 -0
  196. data/spec/factories/routes_factory.rb +10 -0
  197. data/spec/factories/service_instances_factory.rb +10 -0
  198. data/spec/factories/service_plans_factory.rb +10 -0
  199. data/spec/factories/services_factory.rb +10 -0
  200. data/spec/factories/spaces_factory.rb +10 -0
  201. data/spec/factories/user_provided_service_instances_factory.rb +10 -0
  202. data/spec/factories/users_factory.rb +10 -0
  203. data/spec/fixtures/apps/with_cfignore/.cfignore +4 -0
  204. data/spec/fixtures/apps/with_cfignore/.hidden_file +1 -0
  205. data/spec/fixtures/apps/with_cfignore/ambiguous_ignored +0 -0
  206. data/spec/fixtures/apps/with_cfignore/ignored_dir/file_in_ignored_dir.txt +1 -0
  207. data/spec/fixtures/apps/with_cfignore/ignored_file.txt +1 -0
  208. data/spec/fixtures/apps/with_cfignore/non_ignored_dir/file_in_non_ignored_dir.txt +1 -0
  209. data/spec/fixtures/apps/with_cfignore/non_ignored_dir/ignored_file.txt +1 -0
  210. data/spec/fixtures/apps/with_cfignore/non_ignored_dir/toplevel_ignored.txt +0 -0
  211. data/spec/fixtures/apps/with_cfignore/non_ignored_file.txt +1 -0
  212. data/spec/fixtures/apps/with_cfignore/toplevel_ignored.txt +0 -0
  213. data/spec/fixtures/apps/with_dotfiles/.dotfile +1 -0
  214. data/spec/fixtures/apps/with_dotfiles/xyz +1 -0
  215. data/spec/fixtures/apps/with_external_symlink/foo +1 -0
  216. data/spec/fixtures/apps/with_ignored_external_symlink/.cfignore +1 -0
  217. data/spec/fixtures/apps/with_ignored_external_symlink/foo +1 -0
  218. data/spec/fixtures/apps/with_nested_directories/foo/bar/baz/fizz +0 -0
  219. data/spec/fixtures/apps/with_nested_directories/xyz +0 -0
  220. data/spec/fixtures/empty_file +0 -0
  221. data/spec/fixtures/fake_cc_app_usage_events.json +152 -0
  222. data/spec/fixtures/fake_cc_application.json +20 -0
  223. data/spec/fixtures/fake_cc_application_summary.json +56 -0
  224. data/spec/fixtures/fake_cc_created_application.json +11 -0
  225. data/spec/fixtures/fake_cc_created_domain.json +15 -0
  226. data/spec/fixtures/fake_cc_created_organization.json +11 -0
  227. data/spec/fixtures/fake_cc_created_route.json +13 -0
  228. data/spec/fixtures/fake_cc_created_service_instance.json +11 -0
  229. data/spec/fixtures/fake_cc_created_space.json +11 -0
  230. data/spec/fixtures/fake_cc_created_user.json +11 -0
  231. data/spec/fixtures/fake_cc_domain.json +55 -0
  232. data/spec/fixtures/fake_cc_domain_spaces.json +27 -0
  233. data/spec/fixtures/fake_cc_empty_search.json +7 -0
  234. data/spec/fixtures/fake_cc_events.json +419 -0
  235. data/spec/fixtures/fake_cc_frameworks.json +20 -0
  236. data/spec/fixtures/fake_cc_managed_service_instance.json +83 -0
  237. data/spec/fixtures/fake_cc_organization.json +161 -0
  238. data/spec/fixtures/fake_cc_organization_domains.json +59 -0
  239. data/spec/fixtures/fake_cc_organization_search.json +37 -0
  240. data/spec/fixtures/fake_cc_organization_spaces.json +99 -0
  241. data/spec/fixtures/fake_cc_organization_summary.json +20 -0
  242. data/spec/fixtures/fake_cc_organization_users.json +81 -0
  243. data/spec/fixtures/fake_cc_route.json +16 -0
  244. data/spec/fixtures/fake_cc_runtimes.json +20 -0
  245. data/spec/fixtures/fake_cc_service_binding.json +22 -0
  246. data/spec/fixtures/fake_cc_service_bindings.json +24 -0
  247. data/spec/fixtures/fake_cc_service_instance.json +83 -0
  248. data/spec/fixtures/fake_cc_service_instances.json +72 -0
  249. data/spec/fixtures/fake_cc_services.json +160 -0
  250. data/spec/fixtures/fake_cc_space.json +45 -0
  251. data/spec/fixtures/fake_cc_space_apps.json +49 -0
  252. data/spec/fixtures/fake_cc_space_summary.json +84 -0
  253. data/spec/fixtures/fake_cc_spaces.json +92 -0
  254. data/spec/fixtures/fake_cc_stats.json +29 -0
  255. data/spec/fixtures/fake_cc_user.json +139 -0
  256. data/spec/fixtures/fake_cc_user_organizations.json +92 -0
  257. data/spec/fixtures/fake_cc_user_provided_service_instance.json +51 -0
  258. data/spec/fixtures/fake_cc_user_with_managers.json +85 -0
  259. data/spec/integration/client_spec.rb +38 -0
  260. data/spec/spec_helper.rb +22 -0
  261. data/spec/support/factory_girl.rb +6 -0
  262. data/spec/support/shared_examples/cc_api_stub_request_examples.rb +79 -0
  263. data/spec/support/shared_examples/client_login_examples.rb +46 -0
  264. data/spec/support/shared_examples/model_summary_examples.rb +34 -0
  265. data/spec/support/test_model_builder.rb +10 -0
  266. data/vendor/errors/v1.yml +189 -0
  267. data/vendor/errors/v2.yml +845 -0
  268. metadata +712 -0
@@ -0,0 +1,14 @@
1
+ module CFoundry
2
+ module ProxyOptions
3
+ def proxy_options_for(uri)
4
+ proxy_uri = uri.find_proxy
5
+
6
+ if proxy_uri.nil?
7
+ []
8
+ else
9
+ proxy_user, proxy_password = proxy_uri.userinfo.split(/:/) if proxy_uri.userinfo
10
+ [proxy_uri.host, proxy_uri.port, proxy_user, proxy_password]
11
+ end
12
+ end
13
+ end
14
+ end
@@ -0,0 +1,163 @@
1
+ require "net/https"
2
+ require "multi_json"
3
+ require "yaml"
4
+
5
+ module CFoundry
6
+ # Base class for CFoundry errors (not from the server).
7
+ class Error < RuntimeError
8
+ end
9
+
10
+ class Deprecated < Error
11
+ end
12
+
13
+ class Mismatch < Error
14
+ def initialize(expected, got)
15
+ @expected = expected
16
+ @got = got
17
+ end
18
+
19
+ def to_s
20
+ "Invalid value type; expected #{@expected.inspect}, got #{@got.inspect}"
21
+ end
22
+ end
23
+
24
+ class InvalidTarget < Error
25
+ attr_reader :target
26
+
27
+ def initialize(target)
28
+ @target = target
29
+ end
30
+
31
+ def to_s
32
+ "Invalid target URI: #{@target}"
33
+ end
34
+ end
35
+
36
+ class TargetRefused < Error
37
+ # Error message.
38
+ attr_reader :message
39
+
40
+ # Message varies as this represents various network errors.
41
+ def initialize(message)
42
+ @message = message
43
+ end
44
+
45
+ # Exception message.
46
+ def to_s
47
+ "target refused connection (#@message)"
48
+ end
49
+ end
50
+
51
+ class Timeout < Timeout::Error
52
+ attr_reader :method, :uri, :parent
53
+
54
+ def initialize(method, uri, parent = nil)
55
+ @method = method
56
+ @uri = uri
57
+ @parent = parent
58
+ super(to_s)
59
+ end
60
+
61
+ def to_s
62
+ "#{method} #{uri} timed out"
63
+ end
64
+ end
65
+
66
+ # Exception representing errors returned by the API.
67
+ class APIError < RuntimeError
68
+ include TraceHelpers
69
+
70
+ class << self
71
+ def error_classes
72
+ @error_classes ||= {}
73
+ end
74
+ end
75
+
76
+ attr_reader :error_code, :description, :request, :response
77
+
78
+ # Create an APIError with a given request and response.
79
+ def initialize(description = nil, error_code = nil, request = nil, response = nil)
80
+ @response = response
81
+ @request = request
82
+ @error_code = error_code || (response ? response[:status] : nil)
83
+ @description = description || parse_description
84
+ end
85
+
86
+ # Exception message.
87
+ def to_s
88
+ "#{error_code}: #{description}"
89
+ end
90
+
91
+ def request_trace
92
+ super(request)
93
+ end
94
+
95
+ def response_trace
96
+ super(response)
97
+ end
98
+
99
+ private
100
+
101
+ def parse_description
102
+ return unless response
103
+
104
+ parse_json(response[:body])[:description]
105
+ rescue MultiJson::DecodeError
106
+ response[:body]
107
+ end
108
+
109
+ def parse_json(x)
110
+ if x.empty?
111
+ raise MultiJson::DecodeError.new("Empty JSON string", [], "")
112
+ else
113
+ MultiJson.load(x, :symbolize_keys => true)
114
+ end
115
+ end
116
+ end
117
+
118
+ class NotFound < APIError
119
+ end
120
+
121
+ class Denied < APIError
122
+ end
123
+
124
+ class Unauthorized < APIError
125
+ end
126
+
127
+ class BadResponse < APIError
128
+ end
129
+
130
+ class UAAError < APIError
131
+ end
132
+
133
+ def self.define_error(class_name, code)
134
+ base =
135
+ case class_name
136
+ when /NotFound$/
137
+ NotFound
138
+ else
139
+ APIError
140
+ end
141
+
142
+ klass =
143
+ if const_defined?(class_name)
144
+ const_get(class_name)
145
+ else
146
+ Class.new(base)
147
+ end
148
+
149
+ APIError.error_classes[code] = klass
150
+
151
+ unless const_defined?(class_name)
152
+ const_set(class_name, klass)
153
+ end
154
+ end
155
+
156
+ VENDOR_DIR = File.expand_path("../../../vendor", __FILE__)
157
+
158
+ %w{errors/v1.yml errors/v2.yml}.each do |errors|
159
+ YAML.load_file("#{VENDOR_DIR}/#{errors}").each do |code, meta|
160
+ define_error(meta["name"], code)
161
+ end
162
+ end
163
+ end
@@ -0,0 +1,316 @@
1
+ require "cfoundry/trace_helpers"
2
+ require "net/https"
3
+ require "net/http/post/multipart"
4
+ require "multi_json"
5
+ require "fileutils"
6
+
7
+ module CFoundry
8
+ class RestClient
9
+ class HTTPFactory
10
+ def self.create(uri, proxy_options = [])
11
+ http = Net::HTTP.new(uri.host, uri.port, *proxy_options)
12
+
13
+ if uri.is_a?(URI::HTTPS)
14
+ http.use_ssl = true
15
+ http.verify_mode = OpenSSL::SSL::VERIFY_NONE
16
+ end
17
+
18
+ return http
19
+ end
20
+ end
21
+
22
+ include CFoundry::TraceHelpers
23
+ include CFoundry::ProxyOptions
24
+
25
+ LOG_LENGTH = 10
26
+
27
+ HTTP_METHODS = {
28
+ "GET" => Net::HTTP::Get,
29
+ "PUT" => Net::HTTP::Put,
30
+ "POST" => Net::HTTP::Post,
31
+ "DELETE" => Net::HTTP::Delete,
32
+ "HEAD" => Net::HTTP::Head,
33
+ }
34
+
35
+ DEFAULT_OPTIONS = {
36
+ :follow_redirects => true
37
+ }
38
+
39
+ attr_reader :target
40
+
41
+ attr_accessor :trace, :backtrace, :log, :request_id, :token
42
+
43
+ def initialize(target, token = nil)
44
+ @target = target
45
+ @token = token
46
+ @trace = false
47
+ @backtrace = false
48
+ @log = false
49
+ end
50
+
51
+ def target=(target)
52
+ return if target == @target
53
+
54
+ @target = target
55
+ @token = nil
56
+ end
57
+
58
+ def request(method, path, options = {})
59
+ request_uri(method, construct_url(path), DEFAULT_OPTIONS.merge(options))
60
+ end
61
+
62
+ def generate_headers(payload, options)
63
+ headers = {}
64
+
65
+ if payload.is_a?(String)
66
+ headers["Content-Length"] = payload.size
67
+ elsif !payload
68
+ headers["Content-Length"] = 0
69
+ end
70
+
71
+ headers["X-Request-Id"] = @request_id if @request_id
72
+ headers["Authorization"] = @token.auth_header if @token
73
+
74
+ if accept_type = mimetype(options[:accept])
75
+ headers["Accept"] = accept_type
76
+ end
77
+
78
+ if content_type = mimetype(options[:content])
79
+ headers["Content-Type"] = content_type
80
+ end
81
+
82
+ headers.merge!(options[:headers]) if options[:headers]
83
+ headers
84
+ end
85
+
86
+ private
87
+
88
+ def request_uri(method, uri, options = {})
89
+ uri = URI.parse(uri)
90
+
91
+ unless uri.is_a?(URI::HTTP)
92
+ raise InvalidTarget.new(@target)
93
+ end
94
+
95
+ # keep original options in case there's a redirect to follow
96
+ original_options = options.dup
97
+ payload = options[:payload]
98
+
99
+ if options[:params]
100
+ encoded_params = encode_params(options[:params])
101
+ if encoded_params.respond_to?(:empty?) ? !encoded_params.empty? : encoded_params
102
+ if uri.query
103
+ uri.query += "&" + encoded_params
104
+ else
105
+ uri.query = encoded_params
106
+ end
107
+ end
108
+ end
109
+
110
+ unless payload.is_a?(String)
111
+ case options[:content]
112
+ when :json
113
+ payload = MultiJson.dump(payload)
114
+ when :form
115
+ payload = encode_params(payload)
116
+ end
117
+ end
118
+
119
+ method_class = get_method_class(method)
120
+ if payload.is_a?(Hash)
121
+ multipart = method_class.const_get(:Multipart)
122
+ request = multipart.new(uri.request_uri, payload)
123
+ else
124
+ request = method_class.new(uri.request_uri)
125
+ request.body = payload if payload
126
+ end
127
+
128
+ headers = generate_headers(payload, options)
129
+
130
+ request_hash = {
131
+ :url => uri.to_s,
132
+ :method => method,
133
+ :headers => headers,
134
+ :body => payload
135
+ }
136
+
137
+ print_request(request_hash) if @trace
138
+
139
+ add_headers(request, headers)
140
+
141
+ http = HTTPFactory.create(uri, proxy_options_for(uri))
142
+
143
+ # TODO remove this when staging returns streaming responses
144
+ http.read_timeout = 300
145
+
146
+ before = Time.now
147
+ http.start do
148
+ response = http.request(request)
149
+ time = Time.now - before
150
+
151
+ response_hash = {
152
+ :headers => sane_headers(response),
153
+ :status => response.code,
154
+ :body => response.body
155
+ }
156
+
157
+ print_response(response_hash) if @trace
158
+ print_backtrace(caller) if @trace
159
+
160
+ log_request(time, request, response)
161
+
162
+ if response.is_a?(Net::HTTPRedirection) && options[:follow_redirects]
163
+ request_uri("GET", response["location"], original_options)
164
+ else
165
+ return request_hash, response_hash
166
+ end
167
+ end
168
+ rescue ::Timeout::Error => e
169
+ raise Timeout.new(method, uri, e)
170
+ rescue SocketError, Errno::ECONNREFUSED => e
171
+ raise TargetRefused, e.message
172
+ rescue URI::InvalidURIError
173
+ raise InvalidTarget.new(@target)
174
+ end
175
+
176
+ def construct_url(path)
177
+ uri = URI.parse(path)
178
+ return path if uri.scheme
179
+
180
+ path = "/#{path}" unless path[0] == ?\/
181
+ target + path
182
+ end
183
+
184
+ def get_method_class(method_string)
185
+ HTTP_METHODS[method_string.upcase]
186
+ end
187
+
188
+ def add_headers(request, headers)
189
+ headers.each { |key, value| request[key] = value }
190
+ end
191
+
192
+ def mimetype(content)
193
+ case content
194
+ when String
195
+ content
196
+ when :json
197
+ "application/json"
198
+ when :form
199
+ "application/x-www-form-urlencoded"
200
+ when nil
201
+ nil
202
+ # return request headers (not really Accept)
203
+ else
204
+ raise CFoundry::Error, "Unknown mimetype '#{content.inspect}'"
205
+ end
206
+ end
207
+
208
+ def encode_params(hash, escape = true)
209
+ hash.keys.map do |k|
210
+ v = hash[k]
211
+ v = MultiJson.dump(v) if v.is_a?(Hash)
212
+ v = URI.escape(v.to_s, /[^#{URI::PATTERN::UNRESERVED}]/) if escape
213
+ "#{k}=#{v}"
214
+ end.join("&")
215
+ end
216
+
217
+ def log_data(time, request, response)
218
+ { :time => time,
219
+ :request => {
220
+ :method => request.method,
221
+ :url => request.path,
222
+ :headers => sane_headers(request)
223
+ },
224
+ :response => {
225
+ :code => response.code,
226
+ :headers => sane_headers(response)
227
+ }
228
+ }
229
+ end
230
+
231
+ def log_line(io, data)
232
+ io.printf(
233
+ "[%s] %0.3fs %6s -> %d %s\n",
234
+ Time.now.strftime("%F %T"),
235
+ data[:time],
236
+ data[:request][:method].to_s.upcase,
237
+ data[:response][:code],
238
+ data[:request][:url])
239
+ end
240
+
241
+ def log_request(time, request, response)
242
+ return unless @log
243
+
244
+ data = log_data(time, request, response)
245
+
246
+ case @log
247
+ when IO
248
+ log_line(@log, data)
249
+ return
250
+ when String
251
+ if File.exists?(@log)
252
+ log = File.readlines(@log).last(LOG_LENGTH - 1)
253
+ elsif !File.exists?(File.dirname(@log))
254
+ FileUtils.mkdir_p(File.dirname(@log))
255
+ end
256
+
257
+ File.open(@log, "w") do |io|
258
+ log.each { |l| io.print l } if log
259
+ log_line(io, data)
260
+ end
261
+
262
+ return
263
+ end
264
+
265
+ if @log.respond_to?(:call)
266
+ @log.call(data)
267
+ return
268
+ end
269
+
270
+ if @log.respond_to?(:<<)
271
+ @log << data
272
+ return
273
+ end
274
+ end
275
+
276
+ def print_request(request)
277
+ $stderr.puts ">>>"
278
+ $stderr.puts request_trace(request)
279
+ end
280
+
281
+ def print_response(response)
282
+ $stderr.puts response_trace(response)
283
+ $stderr.puts "<<<"
284
+ end
285
+
286
+ def print_backtrace(locs)
287
+ return unless @backtrace
288
+
289
+ interesting_locs = locs.drop_while { |loc|
290
+ loc =~ /\/(cfoundry\/|restclient\/|net\/http)/
291
+ }
292
+
293
+ $stderr.puts "--- backtrace:"
294
+
295
+ $stderr.puts "... (boring)" unless locs == interesting_locs
296
+
297
+ trimmed_locs = interesting_locs[0..5]
298
+
299
+ trimmed_locs.each do |loc|
300
+ $stderr.puts "=== #{loc}"
301
+ end
302
+
303
+ $stderr.puts "... (trimmed)" unless trimmed_locs == interesting_locs
304
+ end
305
+
306
+ def sane_headers(obj)
307
+ hds = {}
308
+
309
+ obj.each_header do |k, v|
310
+ hds[k] = v
311
+ end
312
+
313
+ hds
314
+ end
315
+ end
316
+ end
@@ -0,0 +1,3 @@
1
+ Dir[File.expand_path('../../../spec/{support}/**/*.rb', __FILE__)].each do |file|
2
+ require file unless file =~ /factory_girl/
3
+ end
@@ -0,0 +1,64 @@
1
+ require "net/https"
2
+ require "multi_json"
3
+
4
+ module CFoundry
5
+ module TraceHelpers
6
+ PROTECTED_ATTRIBUTES = ['Authorization', 'credentials']
7
+
8
+ def request_trace(request)
9
+ return nil unless request
10
+ info = ["REQUEST: #{request[:method]} #{request[:url]}"]
11
+ info << "REQUEST_HEADERS:"
12
+ info << header_trace(request[:headers])
13
+ info << "REQUEST_BODY: #{request[:body]}" if request[:body]
14
+ info.join("\n")
15
+ end
16
+
17
+
18
+ def response_trace(response)
19
+ return nil unless response
20
+ info = ["RESPONSE: [#{response[:status]}]"]
21
+ info << "RESPONSE_HEADERS:"
22
+ info << header_trace(response[:headers])
23
+ info << "RESPONSE_BODY:"
24
+ begin
25
+ parsed_body = MultiJson.load(response[:body])
26
+ filter_protected_attributes(parsed_body)
27
+ info << MultiJson.dump(parsed_body, :pretty => true)
28
+ rescue
29
+ info << "#{response[:body]}"
30
+ end
31
+ info.join("\n")
32
+ end
33
+
34
+ private
35
+
36
+ def header_trace(headers)
37
+ headers.sort.map do |key, value|
38
+ unless PROTECTED_ATTRIBUTES.include?(key)
39
+ " #{key} : #{value}"
40
+ else
41
+ " #{key} : [PRIVATE DATA HIDDEN]"
42
+ end
43
+ end
44
+ end
45
+
46
+ def filter_protected_attributes(hash_or_array)
47
+ if hash_or_array.is_a? Array
48
+ hash_or_array.each do |value|
49
+ filter_protected_attributes(value)
50
+ end
51
+ else
52
+ hash_or_array.each do |key, value|
53
+ if PROTECTED_ATTRIBUTES.include? key
54
+ hash_or_array[key] = "[PRIVATE DATA HIDDEN]"
55
+ else
56
+ if value.is_a?(Hash) || value.is_a?(Array)
57
+ filter_protected_attributes(value)
58
+ end
59
+ end
60
+ end
61
+ end
62
+ end
63
+ end
64
+ end
@@ -0,0 +1,151 @@
1
+ require "cfoundry/baseclient"
2
+ require "uaa"
3
+
4
+ module CFoundry
5
+ class UAAClient
6
+ attr_accessor :target, :client_id, :client_secret, :token, :trace
7
+
8
+ def initialize(target, client_id = "cf", options = {})
9
+ @target = target
10
+ @client_id = client_id
11
+ @client_secret = options[:client_secret]
12
+ @uaa_info_client = uaa_info_client_for(target)
13
+ end
14
+
15
+ def prompts
16
+ wrap_uaa_errors do
17
+ @uaa_info_client.server[:prompts]
18
+ end
19
+ end
20
+
21
+ def authorize(credentials)
22
+ wrap_uaa_errors do
23
+ authenticate_with_password_grant(credentials) ||
24
+ authenticate_with_implicit_grant(credentials)
25
+ end
26
+ end
27
+
28
+ def user(guid)
29
+ wrap_uaa_errors do
30
+ scim.get(:user, guid)
31
+ end
32
+ end
33
+
34
+ def users
35
+ wrap_uaa_errors do
36
+ scim.query(:user)
37
+ end
38
+ end
39
+
40
+ def change_password(guid, new, old)
41
+ wrap_uaa_errors do
42
+ scim.change_password(guid, new, old)
43
+ end
44
+ end
45
+
46
+ def password_score(password)
47
+ wrap_uaa_errors do
48
+ response = uaa_info_client_for(uaa_url).password_strength(password)
49
+
50
+ required_score = response[:requiredScore] || 0
51
+ case (response[:score] || 0)
52
+ when 10 then
53
+ :strong
54
+ when required_score..9 then
55
+ :good
56
+ else
57
+ :weak
58
+ end
59
+ end
60
+ end
61
+
62
+ def add_user(email, password, options = {})
63
+ wrap_uaa_errors do
64
+ scim.add(
65
+ :user,
66
+ {:userName => email,
67
+ :emails => [{:value => email}],
68
+ :password => password,
69
+ :name => {:givenName => options[:givenName] || email,
70
+ :familyName => options[:familyName] || email}
71
+ }
72
+ )
73
+ end
74
+ end
75
+
76
+ def delete_user(guid)
77
+ wrap_uaa_errors do
78
+ scim.delete(:user, guid)
79
+ end
80
+ end
81
+
82
+ def try_to_refresh_token!
83
+ wrap_uaa_errors do
84
+ begin
85
+ token_info = token_issuer.refresh_token_grant(token.refresh_token)
86
+ self.token = AuthToken.from_uaa_token_info(token_info)
87
+ rescue CF::UAA::TargetError
88
+ self.token
89
+ end
90
+ end
91
+ end
92
+
93
+ private
94
+
95
+ def uaa_info_client_for(url)
96
+ CF::UAA::Info.new(url, :symbolize_keys => true)
97
+ end
98
+
99
+ def token_issuer
100
+ @token_issuer ||= CF::UAA::TokenIssuer.new(target, client_id, client_secret, :symbolize_keys => true)
101
+ @token_issuer.logger.level = @trace ? Logger::Severity::TRACE : 1
102
+ @token_issuer
103
+ end
104
+
105
+ def scim
106
+ auth_header = token && token.auth_header
107
+ scim = CF::UAA::Scim.new(uaa_url, auth_header, :symbolize_keys => true)
108
+ scim.logger.level = @trace ? Logger::Severity::TRACE : 1
109
+ scim
110
+ end
111
+
112
+ def uaa_url
113
+ @uaa_url ||= @uaa_info_client.discover_uaa
114
+ end
115
+
116
+ def authenticate_with_password_grant(credentials)
117
+ begin
118
+ # Currently owner_password_grant method does not allow
119
+ # non-password based authenticate; so we have cheat a little bit.
120
+ token_issuer.send(:request_token,
121
+ {:grant_type => "password", :scope => nil}.merge(credentials))
122
+ rescue CF::UAA::BadResponse => e
123
+ status_code = e.message[/\d+/] || 400
124
+ raise CFoundry::Denied.new("Authorization failed", status_code)
125
+ rescue CF::UAA::TargetError
126
+ false
127
+ end
128
+ end
129
+
130
+ def authenticate_with_implicit_grant(credentials)
131
+ begin
132
+ token_issuer.implicit_grant_with_creds(credentials)
133
+ rescue CF::UAA::BadResponse => e
134
+ status_code = e.message[/\d+/] || 400
135
+ raise CFoundry::Denied.new("Authorization failed", status_code)
136
+ end
137
+ end
138
+
139
+ def wrap_uaa_errors
140
+ yield
141
+ rescue CF::UAA::BadResponse
142
+ raise CFoundry::BadResponse
143
+ rescue CF::UAA::NotFound
144
+ raise CFoundry::NotFound
145
+ rescue CF::UAA::InvalidToken
146
+ raise CFoundry::Denied
147
+ rescue CF::UAA::TargetError => e
148
+ raise CFoundry::UAAError.new(e.info[:error_description], e.info[:error])
149
+ end
150
+ end
151
+ end