fog-brightbox 1.5.0 → 1.7.1
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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +37 -0
- data/lib/fog/brightbox/compute/shared.rb +26 -10
- data/lib/fog/brightbox/compute.rb +11 -0
- data/lib/fog/brightbox/config.rb +12 -0
- data/lib/fog/brightbox/models/compute/account.rb +30 -18
- data/lib/fog/brightbox/models/compute/api_client.rb +10 -2
- data/lib/fog/brightbox/models/compute/application.rb +8 -0
- data/lib/fog/brightbox/models/compute/cloud_ip.rb +11 -11
- data/lib/fog/brightbox/models/compute/collaboration.rb +11 -0
- data/lib/fog/brightbox/models/compute/config_map.rb +14 -0
- data/lib/fog/brightbox/models/compute/config_maps.rb +22 -0
- data/lib/fog/brightbox/models/compute/database_server.rb +19 -15
- data/lib/fog/brightbox/models/compute/database_snapshot.rb +13 -6
- data/lib/fog/brightbox/models/compute/database_type.rb +6 -3
- data/lib/fog/brightbox/models/compute/event.rb +5 -4
- data/lib/fog/brightbox/models/compute/firewall_policy.rb +10 -4
- data/lib/fog/brightbox/models/compute/firewall_rule.rb +9 -6
- data/lib/fog/brightbox/models/compute/flavor.rb +3 -3
- data/lib/fog/brightbox/models/compute/image.rb +16 -14
- data/lib/fog/brightbox/models/compute/load_balancer.rb +12 -8
- data/lib/fog/brightbox/models/compute/server.rb +19 -17
- data/lib/fog/brightbox/models/compute/server_group.rb +13 -5
- data/lib/fog/brightbox/models/compute/user.rb +9 -4
- data/lib/fog/brightbox/models/compute/user_collaboration.rb +11 -0
- data/lib/fog/brightbox/models/compute/volume.rb +15 -14
- data/lib/fog/brightbox/models/compute/zone.rb +3 -4
- data/lib/fog/brightbox/models/storage/directory.rb +2 -2
- data/lib/fog/brightbox/oauth2.rb +47 -7
- data/lib/fog/brightbox/requests/compute/create_config_map.rb +22 -0
- data/lib/fog/brightbox/requests/compute/delete_config_map.rb +20 -0
- data/lib/fog/brightbox/requests/compute/get_config_map.rb +18 -0
- data/lib/fog/brightbox/requests/compute/list_config_maps.rb +18 -0
- data/lib/fog/brightbox/requests/compute/update_config_map.rb +22 -0
- data/lib/fog/brightbox/version.rb +1 -1
- data/spec/fog/brightbox/compute/credentials_spec.rb +41 -0
- data/spec/fog/brightbox/compute/get_access_token_spec.rb +305 -0
- data/spec/fog/brightbox/compute/two_factor_spec.rb +53 -0
- data/spec/fog/brightbox/oauth2/user_credentials_strategy_spec.rb +20 -0
- metadata +14 -1
@@ -0,0 +1,305 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe Fog::Brightbox::Compute, "#get_access_token" do
|
4
|
+
before do
|
5
|
+
@new_access_token = "0987654321"
|
6
|
+
@new_refresh_token = "5432167890"
|
7
|
+
|
8
|
+
@options = {
|
9
|
+
brightbox_client_id: "app-12345",
|
10
|
+
brightbox_secret: "1234567890",
|
11
|
+
brightbox_username: "jason.null@brightbox.com",
|
12
|
+
brightbox_password: "HR4life",
|
13
|
+
brightbox_support_two_factor: true,
|
14
|
+
brightbox_one_time_password: "123456"
|
15
|
+
}
|
16
|
+
@service = Fog::Brightbox::Compute.new(@options)
|
17
|
+
end
|
18
|
+
|
19
|
+
describe "when user does not have 2FA enabled" do
|
20
|
+
describe "and authenticates correctly" do
|
21
|
+
before do
|
22
|
+
stub_authentication_request(auth_correct: true)
|
23
|
+
|
24
|
+
assert @service.two_factor?
|
25
|
+
end
|
26
|
+
|
27
|
+
describe "without !" do
|
28
|
+
it "updates credentials" do
|
29
|
+
token = @service.get_access_token
|
30
|
+
assert_equal "0987654321", token
|
31
|
+
|
32
|
+
assert_equal token, @service.access_token
|
33
|
+
end
|
34
|
+
end
|
35
|
+
|
36
|
+
describe "with !" do
|
37
|
+
it "updates credentials" do
|
38
|
+
token = @service.get_access_token!
|
39
|
+
assert_equal "0987654321", token
|
40
|
+
|
41
|
+
assert_equal token, @service.access_token
|
42
|
+
end
|
43
|
+
end
|
44
|
+
end
|
45
|
+
|
46
|
+
describe "and authenticates incorrectly" do
|
47
|
+
before do
|
48
|
+
stub_authentication_request(auth_correct: false)
|
49
|
+
|
50
|
+
assert @service.two_factor?
|
51
|
+
end
|
52
|
+
|
53
|
+
describe "without !" do
|
54
|
+
it "returns nil" do
|
55
|
+
assert_nil @service.get_access_token
|
56
|
+
|
57
|
+
assert_nil @service.access_token
|
58
|
+
assert_nil @service.refresh_token
|
59
|
+
end
|
60
|
+
end
|
61
|
+
|
62
|
+
describe "with !" do
|
63
|
+
it "raises an error" do
|
64
|
+
begin
|
65
|
+
@service.get_access_token!
|
66
|
+
rescue Excon::Error::Unauthorized
|
67
|
+
assert_nil @service.access_token
|
68
|
+
assert_nil @service.refresh_token
|
69
|
+
end
|
70
|
+
end
|
71
|
+
end
|
72
|
+
end
|
73
|
+
end
|
74
|
+
|
75
|
+
describe "when user does have 2FA enabled" do
|
76
|
+
describe "and authenticates correctly" do
|
77
|
+
describe "without OTP" do
|
78
|
+
before do
|
79
|
+
stub_authentication_request(auth_correct: true,
|
80
|
+
two_factor_user: true)
|
81
|
+
|
82
|
+
assert @service.two_factor?
|
83
|
+
end
|
84
|
+
|
85
|
+
describe "without !" do
|
86
|
+
it "returns nil" do
|
87
|
+
assert_nil @service.get_access_token
|
88
|
+
|
89
|
+
assert_nil @service.access_token
|
90
|
+
assert_nil @service.refresh_token
|
91
|
+
end
|
92
|
+
end
|
93
|
+
|
94
|
+
describe "with !" do
|
95
|
+
it "raises an error" do
|
96
|
+
begin
|
97
|
+
@service.get_access_token!
|
98
|
+
rescue Fog::Brightbox::OAuth2::TwoFactorMissingError
|
99
|
+
assert_nil @service.access_token
|
100
|
+
assert_nil @service.refresh_token
|
101
|
+
end
|
102
|
+
end
|
103
|
+
end
|
104
|
+
end
|
105
|
+
|
106
|
+
describe "with OTP" do
|
107
|
+
before do
|
108
|
+
stub_authentication_request(auth_correct: true,
|
109
|
+
two_factor_user: true,
|
110
|
+
otp_sent: true)
|
111
|
+
|
112
|
+
assert @service.two_factor?
|
113
|
+
end
|
114
|
+
|
115
|
+
describe "without !" do
|
116
|
+
it "updates credentials" do
|
117
|
+
token = @service.get_access_token
|
118
|
+
assert_equal "0987654321", token
|
119
|
+
|
120
|
+
assert_equal token, @service.access_token
|
121
|
+
end
|
122
|
+
end
|
123
|
+
|
124
|
+
describe "with !" do
|
125
|
+
it "updates credentials" do
|
126
|
+
token = @service.get_access_token!
|
127
|
+
assert_equal "0987654321", token
|
128
|
+
|
129
|
+
assert_equal token, @service.access_token
|
130
|
+
end
|
131
|
+
end
|
132
|
+
end
|
133
|
+
end
|
134
|
+
|
135
|
+
describe "and authenticates incorrectly" do
|
136
|
+
before do
|
137
|
+
stub_authentication_request(auth_correct: false,
|
138
|
+
two_factor_user: true)
|
139
|
+
|
140
|
+
assert @service.two_factor?
|
141
|
+
end
|
142
|
+
|
143
|
+
describe "without !" do
|
144
|
+
it "returns nil" do
|
145
|
+
assert_nil @service.get_access_token
|
146
|
+
|
147
|
+
assert_nil @service.access_token
|
148
|
+
assert_nil @service.refresh_token
|
149
|
+
end
|
150
|
+
end
|
151
|
+
|
152
|
+
describe "with !" do
|
153
|
+
it "raises an error" do
|
154
|
+
begin
|
155
|
+
@service.get_access_token!
|
156
|
+
rescue Fog::Brightbox::OAuth2::TwoFactorMissingError
|
157
|
+
assert_nil @service.access_token
|
158
|
+
assert_nil @service.refresh_token
|
159
|
+
end
|
160
|
+
end
|
161
|
+
end
|
162
|
+
end
|
163
|
+
|
164
|
+
describe "without 2FA support enabled" do
|
165
|
+
before do
|
166
|
+
@options = {
|
167
|
+
brightbox_client_id: "app-12345",
|
168
|
+
brightbox_secret: "1234567890",
|
169
|
+
brightbox_username: "jason.null@brightbox.com",
|
170
|
+
brightbox_password: "HR4life",
|
171
|
+
brightbox_support_two_factor: false,
|
172
|
+
brightbox_one_time_password: "123456"
|
173
|
+
}
|
174
|
+
@service = Fog::Brightbox::Compute.new(@options)
|
175
|
+
|
176
|
+
refute @service.two_factor?
|
177
|
+
end
|
178
|
+
|
179
|
+
describe "without !" do
|
180
|
+
it "returns nil" do
|
181
|
+
stub_authentication_request(auth_correct: false,
|
182
|
+
two_factor_user: true)
|
183
|
+
|
184
|
+
assert_nil @service.get_access_token
|
185
|
+
|
186
|
+
assert_nil @service.access_token
|
187
|
+
assert_nil @service.refresh_token
|
188
|
+
end
|
189
|
+
end
|
190
|
+
|
191
|
+
describe "with !" do
|
192
|
+
describe "when authentication incorrect" do
|
193
|
+
it "raises an error" do
|
194
|
+
stub_authentication_request(auth_correct: false,
|
195
|
+
two_factor_user: true,
|
196
|
+
otp_sent: true)
|
197
|
+
|
198
|
+
begin
|
199
|
+
@service.get_access_token!
|
200
|
+
rescue Excon::Error::Unauthorized
|
201
|
+
assert_nil @service.access_token
|
202
|
+
assert_nil @service.refresh_token
|
203
|
+
end
|
204
|
+
end
|
205
|
+
end
|
206
|
+
|
207
|
+
describe "with missing OTP" do
|
208
|
+
before do
|
209
|
+
@options = {
|
210
|
+
brightbox_client_id: "app-12345",
|
211
|
+
brightbox_secret: "1234567890",
|
212
|
+
brightbox_username: "jason.null@brightbox.com",
|
213
|
+
brightbox_password: "HR4life",
|
214
|
+
brightbox_support_two_factor: false
|
215
|
+
}
|
216
|
+
@service = Fog::Brightbox::Compute.new(@options)
|
217
|
+
|
218
|
+
refute @service.two_factor?
|
219
|
+
end
|
220
|
+
|
221
|
+
it "raises an error" do
|
222
|
+
stub_authentication_request(auth_correct: true,
|
223
|
+
two_factor_user: true,
|
224
|
+
otp_sent: false)
|
225
|
+
|
226
|
+
begin
|
227
|
+
@service.get_access_token!
|
228
|
+
rescue Excon::Error::Unauthorized
|
229
|
+
assert_nil @service.access_token
|
230
|
+
assert_nil @service.refresh_token
|
231
|
+
end
|
232
|
+
end
|
233
|
+
end
|
234
|
+
end
|
235
|
+
end
|
236
|
+
end
|
237
|
+
|
238
|
+
# @param auth_correct [Boolean] Treat username/password as correct
|
239
|
+
# @param two_factor_user [Boolean] Is user protected by 2FA on server
|
240
|
+
# @param otp_sent [Boolean] Is an OTP sent in the request?
|
241
|
+
def stub_authentication_request(auth_correct:,
|
242
|
+
two_factor_user: false,
|
243
|
+
otp_sent: nil)
|
244
|
+
|
245
|
+
two_factor_supported = @service.two_factor?
|
246
|
+
|
247
|
+
request = {
|
248
|
+
headers: {
|
249
|
+
"Authorization" => "Basic YXBwLTEyMzQ1OjEyMzQ1Njc4OTA=",
|
250
|
+
"Content-Type" => "application/json",
|
251
|
+
},
|
252
|
+
body: {
|
253
|
+
grant_type: "password",
|
254
|
+
username: "jason.null@brightbox.com",
|
255
|
+
password: "HR4life"
|
256
|
+
}.to_json
|
257
|
+
}.tap do |req|
|
258
|
+
# Only expect the header if the service should send it
|
259
|
+
# Without this, we stub a request that demands an OTP but it is not sent
|
260
|
+
if two_factor_supported && otp_sent
|
261
|
+
req[:headers]["X-Brightbox-OTP"] = "123456"
|
262
|
+
end
|
263
|
+
end
|
264
|
+
|
265
|
+
# The cases we are testing are:
|
266
|
+
#
|
267
|
+
# * User does not use 2FA and authenticate
|
268
|
+
# * User does not use 2FA and FAILS to authenticate
|
269
|
+
# * User does use 2FA and authenticate
|
270
|
+
# * User does use 2FA and FAILS to authenticate
|
271
|
+
# * User does use 2FA and FAILS to send OTP
|
272
|
+
#
|
273
|
+
response = if two_factor_user && !otp_sent
|
274
|
+
# OTP required header
|
275
|
+
{
|
276
|
+
status: 401,
|
277
|
+
headers: {
|
278
|
+
"X-Brightbox-OTP" => "required"
|
279
|
+
},
|
280
|
+
body: { error: "invalid_client" }.to_json
|
281
|
+
}
|
282
|
+
elsif !auth_correct
|
283
|
+
# No OTP header
|
284
|
+
{
|
285
|
+
status: 401,
|
286
|
+
headers: {},
|
287
|
+
body: { error: "invalid_client" }.to_json
|
288
|
+
}
|
289
|
+
else
|
290
|
+
{
|
291
|
+
status: 200,
|
292
|
+
headers: {},
|
293
|
+
body: {
|
294
|
+
access_token: @new_access_token,
|
295
|
+
refresh_token: @new_refresh_token,
|
296
|
+
expires_in: 7200
|
297
|
+
}.to_json
|
298
|
+
}
|
299
|
+
end
|
300
|
+
|
301
|
+
stub_request(:post, "https://api.gb1.brightbox.com/token")
|
302
|
+
.with(request)
|
303
|
+
.to_return(response)
|
304
|
+
end
|
305
|
+
end
|
@@ -0,0 +1,53 @@
|
|
1
|
+
require "spec_helper"
|
2
|
+
|
3
|
+
describe Fog::Brightbox::Compute, "#two_factor?" do
|
4
|
+
describe "when omitted" do
|
5
|
+
before do
|
6
|
+
@options = {
|
7
|
+
brightbox_client_id: "app-12345",
|
8
|
+
brightbox_secret: "1234567890",
|
9
|
+
brightbox_username: "jason.null@brightbox.com",
|
10
|
+
brightbox_password: "HR4life"
|
11
|
+
}
|
12
|
+
@service = Fog::Brightbox::Compute.new(@options)
|
13
|
+
end
|
14
|
+
|
15
|
+
it do
|
16
|
+
refute @service.two_factor?
|
17
|
+
end
|
18
|
+
end
|
19
|
+
|
20
|
+
describe "when disabled" do
|
21
|
+
before do
|
22
|
+
@options = {
|
23
|
+
brightbox_client_id: "app-12345",
|
24
|
+
brightbox_secret: "1234567890",
|
25
|
+
brightbox_username: "jason.null@brightbox.com",
|
26
|
+
brightbox_password: "HR4life",
|
27
|
+
brightbox_support_two_factor: false
|
28
|
+
}
|
29
|
+
@service = Fog::Brightbox::Compute.new(@options)
|
30
|
+
end
|
31
|
+
|
32
|
+
it do
|
33
|
+
refute @service.two_factor?
|
34
|
+
end
|
35
|
+
end
|
36
|
+
|
37
|
+
describe "when enabled" do
|
38
|
+
before do
|
39
|
+
@options = {
|
40
|
+
brightbox_client_id: "app-12345",
|
41
|
+
brightbox_secret: "1234567890",
|
42
|
+
brightbox_username: "jason.null@brightbox.com",
|
43
|
+
brightbox_password: "HR4life",
|
44
|
+
brightbox_support_two_factor: true
|
45
|
+
}
|
46
|
+
@service = Fog::Brightbox::Compute.new(@options)
|
47
|
+
end
|
48
|
+
|
49
|
+
it do
|
50
|
+
assert @service.two_factor?
|
51
|
+
end
|
52
|
+
end
|
53
|
+
end
|
@@ -37,4 +37,24 @@ describe Fog::Brightbox::OAuth2::UserCredentialsStrategy do
|
|
37
37
|
assert_equal "Basic YXBwLTEyMzQ1Ol9fbWFzaGVkX2tleXNfMTIzX18=", headers["Authorization"]
|
38
38
|
assert_equal "application/json", headers["Content-Type"]
|
39
39
|
end
|
40
|
+
|
41
|
+
describe "when 2FA OTP is included" do
|
42
|
+
before do
|
43
|
+
options = {
|
44
|
+
username: @username,
|
45
|
+
password: @password,
|
46
|
+
one_time_password: "123456"
|
47
|
+
}
|
48
|
+
|
49
|
+
@credentials = Fog::Brightbox::OAuth2::CredentialSet.new(@client_id, @client_secret, options)
|
50
|
+
@strategy = Fog::Brightbox::OAuth2::UserCredentialsStrategy.new(@credentials)
|
51
|
+
end
|
52
|
+
|
53
|
+
it "tests #headers" do
|
54
|
+
headers = @strategy.headers
|
55
|
+
assert_equal "Basic YXBwLTEyMzQ1Ol9fbWFzaGVkX2tleXNfMTIzX18=", headers["Authorization"]
|
56
|
+
assert_equal "application/json", headers["Content-Type"]
|
57
|
+
assert_equal "123456", headers["X-Brightbox-OTP"]
|
58
|
+
end
|
59
|
+
end
|
40
60
|
end
|
metadata
CHANGED
@@ -1,7 +1,7 @@
|
|
1
1
|
--- !ruby/object:Gem::Specification
|
2
2
|
name: fog-brightbox
|
3
3
|
version: !ruby/object:Gem::Version
|
4
|
-
version: 1.
|
4
|
+
version: 1.7.1
|
5
5
|
platform: ruby
|
6
6
|
authors:
|
7
7
|
- Paul Thornthwaite
|
@@ -193,6 +193,8 @@ files:
|
|
193
193
|
- lib/fog/brightbox/models/compute/cloud_ips.rb
|
194
194
|
- lib/fog/brightbox/models/compute/collaboration.rb
|
195
195
|
- lib/fog/brightbox/models/compute/collaborations.rb
|
196
|
+
- lib/fog/brightbox/models/compute/config_map.rb
|
197
|
+
- lib/fog/brightbox/models/compute/config_maps.rb
|
196
198
|
- lib/fog/brightbox/models/compute/database_server.rb
|
197
199
|
- lib/fog/brightbox/models/compute/database_servers.rb
|
198
200
|
- lib/fog/brightbox/models/compute/database_snapshot.rb
|
@@ -240,6 +242,7 @@ files:
|
|
240
242
|
- lib/fog/brightbox/requests/compute/create_application.rb
|
241
243
|
- lib/fog/brightbox/requests/compute/create_cloud_ip.rb
|
242
244
|
- lib/fog/brightbox/requests/compute/create_collaboration.rb
|
245
|
+
- lib/fog/brightbox/requests/compute/create_config_map.rb
|
243
246
|
- lib/fog/brightbox/requests/compute/create_database_server.rb
|
244
247
|
- lib/fog/brightbox/requests/compute/create_firewall_policy.rb
|
245
248
|
- lib/fog/brightbox/requests/compute/create_firewall_rule.rb
|
@@ -252,6 +255,7 @@ files:
|
|
252
255
|
- lib/fog/brightbox/requests/compute/delete_application.rb
|
253
256
|
- lib/fog/brightbox/requests/compute/delete_cloud_ip.rb
|
254
257
|
- lib/fog/brightbox/requests/compute/delete_collaboration.rb
|
258
|
+
- lib/fog/brightbox/requests/compute/delete_config_map.rb
|
255
259
|
- lib/fog/brightbox/requests/compute/delete_database_server.rb
|
256
260
|
- lib/fog/brightbox/requests/compute/delete_database_snapshot.rb
|
257
261
|
- lib/fog/brightbox/requests/compute/delete_firewall_policy.rb
|
@@ -269,6 +273,7 @@ files:
|
|
269
273
|
- lib/fog/brightbox/requests/compute/get_authenticated_user.rb
|
270
274
|
- lib/fog/brightbox/requests/compute/get_cloud_ip.rb
|
271
275
|
- lib/fog/brightbox/requests/compute/get_collaboration.rb
|
276
|
+
- lib/fog/brightbox/requests/compute/get_config_map.rb
|
272
277
|
- lib/fog/brightbox/requests/compute/get_database_server.rb
|
273
278
|
- lib/fog/brightbox/requests/compute/get_database_snapshot.rb
|
274
279
|
- lib/fog/brightbox/requests/compute/get_database_type.rb
|
@@ -290,6 +295,7 @@ files:
|
|
290
295
|
- lib/fog/brightbox/requests/compute/list_applications.rb
|
291
296
|
- lib/fog/brightbox/requests/compute/list_cloud_ips.rb
|
292
297
|
- lib/fog/brightbox/requests/compute/list_collaborations.rb
|
298
|
+
- lib/fog/brightbox/requests/compute/list_config_maps.rb
|
293
299
|
- lib/fog/brightbox/requests/compute/list_database_servers.rb
|
294
300
|
- lib/fog/brightbox/requests/compute/list_database_snapshots.rb
|
295
301
|
- lib/fog/brightbox/requests/compute/list_database_types.rb
|
@@ -342,6 +348,7 @@ files:
|
|
342
348
|
- lib/fog/brightbox/requests/compute/update_api_client.rb
|
343
349
|
- lib/fog/brightbox/requests/compute/update_application.rb
|
344
350
|
- lib/fog/brightbox/requests/compute/update_cloud_ip.rb
|
351
|
+
- lib/fog/brightbox/requests/compute/update_config_map.rb
|
345
352
|
- lib/fog/brightbox/requests/compute/update_database_server.rb
|
346
353
|
- lib/fog/brightbox/requests/compute/update_database_snapshot.rb
|
347
354
|
- lib/fog/brightbox/requests/compute/update_firewall_policy.rb
|
@@ -381,6 +388,9 @@ files:
|
|
381
388
|
- lib/fog/brightbox/storage/not_found.rb
|
382
389
|
- lib/fog/brightbox/version.rb
|
383
390
|
- spec/fog/brightbox/compute/config_spec.rb
|
391
|
+
- spec/fog/brightbox/compute/credentials_spec.rb
|
392
|
+
- spec/fog/brightbox/compute/get_access_token_spec.rb
|
393
|
+
- spec/fog/brightbox/compute/two_factor_spec.rb
|
384
394
|
- spec/fog/brightbox/compute/wrapped_request_spec.rb
|
385
395
|
- spec/fog/brightbox/config_spec.rb
|
386
396
|
- spec/fog/brightbox/link_helper_spec.rb
|
@@ -488,6 +498,9 @@ summary: This library can be used as a module for `fog` or as standalone provide
|
|
488
498
|
to use the Brightbox Cloud in applications
|
489
499
|
test_files:
|
490
500
|
- spec/fog/brightbox/compute/config_spec.rb
|
501
|
+
- spec/fog/brightbox/compute/credentials_spec.rb
|
502
|
+
- spec/fog/brightbox/compute/get_access_token_spec.rb
|
503
|
+
- spec/fog/brightbox/compute/two_factor_spec.rb
|
491
504
|
- spec/fog/brightbox/compute/wrapped_request_spec.rb
|
492
505
|
- spec/fog/brightbox/config_spec.rb
|
493
506
|
- spec/fog/brightbox/link_helper_spec.rb
|