aerospike 2.20.1 → 2.22.0
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 +22 -0
- data/lib/aerospike/client.rb +46 -2
- data/lib/aerospike/cluster/create_connection.rb +1 -1
- data/lib/aerospike/cluster.rb +20 -4
- data/lib/aerospike/command/admin_command.rb +362 -54
- data/lib/aerospike/command/command.rb +0 -6
- data/lib/aerospike/command/login_command.rb +164 -0
- data/lib/aerospike/connection/authenticate.rb +36 -3
- data/lib/aerospike/node_validator.rb +6 -1
- data/lib/aerospike/policy/auth_mode.rb +36 -0
- data/lib/aerospike/policy/client_policy.rb +4 -1
- data/lib/aerospike/privilege.rb +133 -0
- data/lib/aerospike/result_code.rb +4 -4
- data/lib/aerospike/role.rb +55 -0
- data/lib/aerospike/user_role.rb +25 -0
- data/lib/aerospike/utils/buffer.rb +21 -0
- data/lib/aerospike/version.rb +1 -1
- data/lib/aerospike.rb +4 -0
- metadata +6 -3
- data/lib/aerospike/command/roles.rb +0 -39
@@ -18,17 +18,22 @@ module Aerospike
|
|
18
18
|
|
19
19
|
private
|
20
20
|
# Commands
|
21
|
-
AUTHENTICATE
|
22
|
-
CREATE_USER
|
23
|
-
DROP_USER
|
24
|
-
SET_PASSWORD
|
25
|
-
CHANGE_PASSWORD
|
26
|
-
GRANT_ROLES
|
27
|
-
REVOKE_ROLES
|
28
|
-
|
29
|
-
|
30
|
-
|
31
|
-
|
21
|
+
AUTHENTICATE = 0
|
22
|
+
CREATE_USER = 1
|
23
|
+
DROP_USER = 2
|
24
|
+
SET_PASSWORD = 3
|
25
|
+
CHANGE_PASSWORD = 4
|
26
|
+
GRANT_ROLES = 5
|
27
|
+
REVOKE_ROLES = 6
|
28
|
+
QUERY_USERS = 9
|
29
|
+
CREATE_ROLE = 10
|
30
|
+
DROP_ROLE = 11
|
31
|
+
GRANT_PRIVILEGES = 12
|
32
|
+
REVOKE_PRIVILEGES = 13
|
33
|
+
SET_WHITELIST = 14
|
34
|
+
SET_QUOTAS = 15
|
35
|
+
QUERY_ROLES = 16
|
36
|
+
LOGIN = 20
|
32
37
|
|
33
38
|
# Field IDs
|
34
39
|
USER = 0
|
@@ -36,8 +41,17 @@ module Aerospike
|
|
36
41
|
OLD_PASSWORD = 2
|
37
42
|
CREDENTIAL = 3
|
38
43
|
CLEAR_PASSWORD = 4
|
44
|
+
SESSION_TOKEN = 5
|
45
|
+
SESSION_TTL = 6
|
39
46
|
ROLES = 10
|
40
|
-
|
47
|
+
ROLE = 11
|
48
|
+
PRIVILEGES = 12
|
49
|
+
ALLOWLIST = 13
|
50
|
+
READ_QUOTA = 14
|
51
|
+
WRITE_QUOTA = 15
|
52
|
+
READ_INFO = 16
|
53
|
+
WRITE_INFO = 17
|
54
|
+
CONNECTIONS = 18
|
41
55
|
|
42
56
|
# Misc
|
43
57
|
MSG_VERSION = 2
|
@@ -55,34 +69,6 @@ module Aerospike
|
|
55
69
|
@data_offset = 8
|
56
70
|
end
|
57
71
|
|
58
|
-
def authenticate(conn, user, password)
|
59
|
-
begin
|
60
|
-
set_authenticate(user, password)
|
61
|
-
conn.write(@data_buffer, @data_offset)
|
62
|
-
conn.read(@data_buffer, HEADER_SIZE)
|
63
|
-
|
64
|
-
result = @data_buffer.read(RESULT_CODE)
|
65
|
-
|
66
|
-
# read the rest of the buffer
|
67
|
-
size = @data_buffer.read_int64(0)
|
68
|
-
length = (size & 0xFFFFFFFFFFFF) - HEADER_REMAINING
|
69
|
-
conn.read(@data_buffer, length)
|
70
|
-
|
71
|
-
raise Exceptions::Aerospike.new(result, "Authentication failed") if result != 0
|
72
|
-
ensure
|
73
|
-
Buffer.put(@data_buffer)
|
74
|
-
end
|
75
|
-
end
|
76
|
-
|
77
|
-
def set_authenticate(user, password)
|
78
|
-
write_header(LOGIN, 2)
|
79
|
-
write_field_str(USER, user)
|
80
|
-
write_field_bytes(CREDENTIAL, password)
|
81
|
-
write_size
|
82
|
-
|
83
|
-
return @data_offset
|
84
|
-
end
|
85
|
-
|
86
72
|
def create_user(cluster, policy, user, password, roles)
|
87
73
|
write_header(CREATE_USER, 3)
|
88
74
|
write_field_str(USER, user)
|
@@ -126,6 +112,61 @@ module Aerospike
|
|
126
112
|
execute_command(cluster, policy)
|
127
113
|
end
|
128
114
|
|
115
|
+
def create_role(cluster, policy, role_name, privileges = [], allowlist = [], read_quota = 0, write_quota = 0)
|
116
|
+
field_count = 1
|
117
|
+
field_count += 1 if privileges.size > 0
|
118
|
+
field_count += 1 if allowlist.size > 0
|
119
|
+
field_count += 1 if read_quota > 0
|
120
|
+
field_count += 1 if write_quota > 0
|
121
|
+
|
122
|
+
write_header(CREATE_ROLE, field_count)
|
123
|
+
write_field_str(ROLE, role_name)
|
124
|
+
|
125
|
+
write_privileges(privileges) if privileges.size > 0
|
126
|
+
write_allowlist(allowlist) if allowlist.size > 0
|
127
|
+
|
128
|
+
write_field_uint32(READ_QUOTA, read_quota) if read_quota > 0
|
129
|
+
write_field_uint32(WRITE_QUOTA, write_quota) if write_quota > 0
|
130
|
+
|
131
|
+
execute_command(cluster, policy)
|
132
|
+
end
|
133
|
+
|
134
|
+
def drop_role(cluster, policy, role)
|
135
|
+
write_header(DROP_ROLE, 1)
|
136
|
+
write_field_str(ROLE, role)
|
137
|
+
execute_command(cluster, policy)
|
138
|
+
end
|
139
|
+
|
140
|
+
def grant_privileges(cluster, policy, role, privileges)
|
141
|
+
write_header(GRANT_PRIVILEGES, 2)
|
142
|
+
write_field_str(ROLE, role)
|
143
|
+
write_privileges(privileges)
|
144
|
+
execute_command(cluster, policy)
|
145
|
+
end
|
146
|
+
|
147
|
+
def revoke_privileges(cluster, policy, role, privileges)
|
148
|
+
write_header(REVOKE_PRIVILEGES, 2)
|
149
|
+
write_field_str(ROLE, role)
|
150
|
+
write_privileges(privileges)
|
151
|
+
execute_command(cluster, policy)
|
152
|
+
end
|
153
|
+
|
154
|
+
def set_allowlist(cluster, policy, role, allowlist = [])
|
155
|
+
field_count = 1
|
156
|
+
field_count += 1 if allowlist.size > 0
|
157
|
+
write_header(SET_WHITELIST, field_count)
|
158
|
+
write_allowlist(allowlist) if allowlist.size > 0
|
159
|
+
execute_command(cluster, policy)
|
160
|
+
end
|
161
|
+
|
162
|
+
def set_quotas(cluster, policy, role, read_quota, write_quota)
|
163
|
+
write_header(SET_QUOTAS, 3)
|
164
|
+
write_field_str(ROLE, role)
|
165
|
+
write_field_uint32(READ_QUOTA, read_quota)
|
166
|
+
write_field_uint32(WRITE_QUOTA, write_quota)
|
167
|
+
execute_command(cluster, policy)
|
168
|
+
end
|
169
|
+
|
129
170
|
def query_user(cluster, policy, user)
|
130
171
|
# TODO: Remove the workaround in the future
|
131
172
|
sleep(0.010)
|
@@ -152,6 +193,32 @@ module Aerospike
|
|
152
193
|
end
|
153
194
|
end
|
154
195
|
|
196
|
+
def query_role(cluster, policy, role)
|
197
|
+
# TODO: Remove the workaround in the future
|
198
|
+
sleep(0.010)
|
199
|
+
|
200
|
+
list = []
|
201
|
+
begin
|
202
|
+
write_header(QUERY_ROLES, 1)
|
203
|
+
write_field_str(ROLE, role)
|
204
|
+
list = read_roles(cluster, policy)
|
205
|
+
return (list.is_a?(Array) && list.length > 0 ? list.first : nil)
|
206
|
+
ensure
|
207
|
+
Buffer.put(@data_buffer)
|
208
|
+
end
|
209
|
+
end
|
210
|
+
|
211
|
+
def query_roles(cluster, policy)
|
212
|
+
# TODO: Remove the workaround in the future
|
213
|
+
sleep(0.010)
|
214
|
+
begin
|
215
|
+
write_header(QUERY_ROLES, 0)
|
216
|
+
return read_roles(cluster, policy)
|
217
|
+
ensure
|
218
|
+
Buffer.put(@data_buffer)
|
219
|
+
end
|
220
|
+
end
|
221
|
+
|
155
222
|
def write_roles(roles)
|
156
223
|
offset = @data_offset + FIELD_HEADER_SIZE
|
157
224
|
@data_buffer.write_byte(roles.length.ord, offset)
|
@@ -174,6 +241,54 @@ module Aerospike
|
|
174
241
|
@data_buffer.write_int64(size, 0)
|
175
242
|
end
|
176
243
|
|
244
|
+
def write_privileges(privileges)
|
245
|
+
offset = @data_offset
|
246
|
+
@data_offset += FIELD_HEADER_SIZE
|
247
|
+
write_byte(privileges.size)
|
248
|
+
|
249
|
+
for privilege in privileges
|
250
|
+
write_byte(privilege.to_code)
|
251
|
+
if privilege.can_scope?
|
252
|
+
if privilege.set_name.to_s.size > 0 && privilege.namespace.to_s.size == 0
|
253
|
+
raise Aerospike::Exceptions::Aerospike.new(Aerospike::ResultCode::INVALID_PRIVILEGE, "Admin privilege #{privilege.namespace} has a set scope with an empty namespace")
|
254
|
+
end
|
255
|
+
|
256
|
+
write_str(privilege.namespace.to_s)
|
257
|
+
write_str(privilege.set_name.to_s)
|
258
|
+
else
|
259
|
+
if privilege.set_name.to_s.bytesize > 0 || privilege.namespace.to_s.bytesize > 0
|
260
|
+
raise Aerospike::Exceptions::Aerospike.new(Aerospike::ResultCode::INVALID_PRIVILEGE, "Admin global privilege #{privilege} can't have a namespace or set")
|
261
|
+
end
|
262
|
+
end
|
263
|
+
end
|
264
|
+
|
265
|
+
size = @data_offset - offset - FIELD_HEADER_SIZE
|
266
|
+
@data_offset = offset
|
267
|
+
write_field_header(PRIVILEGES, size)
|
268
|
+
@data_offset += size
|
269
|
+
end
|
270
|
+
|
271
|
+
def write_allowlist(allowlist)
|
272
|
+
offset = @data_offset
|
273
|
+
@data_offset += FIELD_HEADER_SIZE
|
274
|
+
|
275
|
+
comma = false
|
276
|
+
for addr in allowlist
|
277
|
+
if comma
|
278
|
+
write_byte(",")
|
279
|
+
else
|
280
|
+
comma = true
|
281
|
+
end
|
282
|
+
|
283
|
+
@data_offset += @data_buffer.write_binary(addr, @data_offset)
|
284
|
+
end
|
285
|
+
|
286
|
+
size = @data_offset - offset - FIELD_HEADER_SIZE
|
287
|
+
@data_offset = offset
|
288
|
+
write_field_header(ALLOWLIST, size)
|
289
|
+
@data_offset += size
|
290
|
+
end
|
291
|
+
|
177
292
|
def write_header(command, field_count)
|
178
293
|
# Authenticate header is almost all zeros
|
179
294
|
i = @data_offset
|
@@ -186,12 +301,27 @@ module Aerospike
|
|
186
301
|
@data_offset += 16
|
187
302
|
end
|
188
303
|
|
304
|
+
def write_byte(b)
|
305
|
+
@data_offset += @data_buffer.write_byte(b, @data_offset)
|
306
|
+
end
|
307
|
+
|
308
|
+
def write_str(str)
|
309
|
+
@data_offset += @data_buffer.write_byte(str.bytesize, @data_offset)
|
310
|
+
@data_offset += @data_buffer.write_binary(str, @data_offset)
|
311
|
+
end
|
312
|
+
|
189
313
|
def write_field_str(id, str)
|
190
314
|
len = @data_buffer.write_binary(str, @data_offset+FIELD_HEADER_SIZE)
|
191
315
|
write_field_header(id, len)
|
192
316
|
@data_offset += len
|
193
317
|
end
|
194
318
|
|
319
|
+
def write_field_uint32(id, val)
|
320
|
+
len = @data_buffer.write_uint32(val, @data_offset+FIELD_HEADER_SIZE)
|
321
|
+
write_field_header(id, len)
|
322
|
+
@data_offset += len
|
323
|
+
end
|
324
|
+
|
195
325
|
def write_field_bytes(id, bytes)
|
196
326
|
@data_buffer.write_binary(bytes, @data_offset+FIELD_HEADER_SIZE)
|
197
327
|
write_field_header(id, bytes.bytesize)
|
@@ -251,7 +381,7 @@ module Aerospike
|
|
251
381
|
raise e
|
252
382
|
end
|
253
383
|
|
254
|
-
raise Exceptions::Aerospike.new(
|
384
|
+
raise Exceptions::Aerospike.new(status) if status > 0
|
255
385
|
|
256
386
|
return list
|
257
387
|
end
|
@@ -292,7 +422,7 @@ module Aerospike
|
|
292
422
|
return (result_code == QUERY_END ? -1 : result_code)
|
293
423
|
end
|
294
424
|
|
295
|
-
|
425
|
+
user_roles = UserRoles.new
|
296
426
|
field_count = @data_buffer.read(@data_offset+3)
|
297
427
|
@data_offset += HEADER_REMAINING
|
298
428
|
|
@@ -306,10 +436,17 @@ module Aerospike
|
|
306
436
|
|
307
437
|
case id
|
308
438
|
when USER
|
309
|
-
|
439
|
+
user_roles.user = @data_buffer.read(@data_offset, len)
|
310
440
|
@data_offset += len
|
311
441
|
when ROLES
|
312
|
-
parse_roles(
|
442
|
+
parse_roles(user_roles)
|
443
|
+
when READ_INFO
|
444
|
+
user_roles.read_info = parse_info
|
445
|
+
when WRITE_INFO
|
446
|
+
user_roles.write_info = parse_info
|
447
|
+
when CONNECTIONS
|
448
|
+
user_roles.conns_in_use = @data_buffer.read_int32(@data_offset)
|
449
|
+
@data_offset += len
|
313
450
|
else
|
314
451
|
@data_offset += len
|
315
452
|
end
|
@@ -317,19 +454,19 @@ module Aerospike
|
|
317
454
|
i = i.succ
|
318
455
|
end
|
319
456
|
|
320
|
-
next if
|
457
|
+
next if user_roles.user == "" && user_roles.roles == nil
|
321
458
|
|
322
|
-
|
323
|
-
list <<
|
459
|
+
user_roles.roles = [] if user_roles.roles == nil
|
460
|
+
list << user_roles
|
324
461
|
end
|
325
462
|
|
326
463
|
return 0, list
|
327
464
|
end
|
328
465
|
|
329
|
-
def parse_roles(
|
466
|
+
def parse_roles(user_roles)
|
330
467
|
size = @data_buffer.read(@data_offset)
|
331
468
|
@data_offset += 1
|
332
|
-
|
469
|
+
user_roles.roles = []
|
333
470
|
|
334
471
|
i = 0
|
335
472
|
while i < size
|
@@ -337,17 +474,188 @@ module Aerospike
|
|
337
474
|
@data_offset += 1
|
338
475
|
role = @data_buffer.read(@data_offset, len)
|
339
476
|
@data_offset += len
|
340
|
-
|
477
|
+
user_roles.roles << role
|
341
478
|
|
342
479
|
i = i.succ
|
343
480
|
end
|
344
481
|
end
|
345
482
|
|
346
|
-
|
347
|
-
|
348
|
-
|
349
|
-
|
483
|
+
def parse_info
|
484
|
+
size = @data_buffer.read(@data_offset)
|
485
|
+
@data_offset += 1
|
486
|
+
list = []
|
487
|
+
|
488
|
+
i = 0
|
489
|
+
while i < size
|
490
|
+
val = @data_buffer.read_int32(@data_offset)
|
491
|
+
@data_offset += 4
|
492
|
+
list << val
|
493
|
+
|
494
|
+
i = i.succ
|
495
|
+
end
|
496
|
+
|
497
|
+
list
|
350
498
|
end
|
499
|
+
|
500
|
+
def read_roles(cluster, policy)
|
501
|
+
write_size
|
502
|
+
node = cluster.random_node
|
503
|
+
|
504
|
+
timeout = 1
|
505
|
+
timeout = policy.timeout if policy != nil && policy.timeout > 0
|
506
|
+
|
507
|
+
status = -1
|
508
|
+
list = []
|
509
|
+
begin
|
510
|
+
conn = node.get_connection(timeout)
|
511
|
+
conn.write(@data_buffer, @data_offset)
|
512
|
+
status, list = read_role_blocks(conn)
|
513
|
+
node.put_connection(conn)
|
514
|
+
rescue => e
|
515
|
+
conn.close if conn
|
516
|
+
raise e
|
517
|
+
end
|
518
|
+
|
519
|
+
raise Exceptions::Aerospike.new(status) if status > 0
|
520
|
+
|
521
|
+
return list
|
522
|
+
end
|
523
|
+
|
524
|
+
def read_role_blocks(conn)
|
525
|
+
rlist = []
|
526
|
+
status = 0
|
527
|
+
begin
|
528
|
+
while status == 0
|
529
|
+
conn.read(@data_buffer, 8)
|
530
|
+
size = @data_buffer.read_int64(0)
|
531
|
+
receive_size = (size & 0xFFFFFFFFFFFF)
|
532
|
+
|
533
|
+
if receive_size > 0
|
534
|
+
@data_buffer.resize(receive_size) if receive_size > @data_buffer.size
|
535
|
+
|
536
|
+
conn.read(@data_buffer, receive_size)
|
537
|
+
status, list = parse_roles_full(receive_size)
|
538
|
+
rlist.concat(list.to_a)
|
539
|
+
else
|
540
|
+
break
|
541
|
+
end
|
542
|
+
end
|
543
|
+
return status, rlist
|
544
|
+
rescue => e
|
545
|
+
return -1, []
|
546
|
+
end
|
547
|
+
end
|
548
|
+
|
549
|
+
def parse_roles_full(receive_size)
|
550
|
+
@data_offset = 0
|
551
|
+
list = []
|
552
|
+
|
553
|
+
while @data_offset < receive_size
|
554
|
+
result_code = @data_buffer.read(@data_offset+1)
|
555
|
+
|
556
|
+
if result_code != 0
|
557
|
+
return (result_code == QUERY_END ? -1 : result_code)
|
558
|
+
end
|
559
|
+
|
560
|
+
role = Role.new
|
561
|
+
field_count = @data_buffer.read(@data_offset+3)
|
562
|
+
@data_offset += HEADER_REMAINING
|
563
|
+
|
564
|
+
i = 0
|
565
|
+
while i < field_count
|
566
|
+
len = @data_buffer.read_int32(@data_offset)
|
567
|
+
@data_offset += 4
|
568
|
+
id = @data_buffer.read(@data_offset)
|
569
|
+
@data_offset += 1
|
570
|
+
len -= 1
|
571
|
+
|
572
|
+
case id
|
573
|
+
when ROLE
|
574
|
+
role.name = @data_buffer.read(@data_offset, len).to_s
|
575
|
+
@data_offset += len
|
576
|
+
when PRIVILEGES
|
577
|
+
parse_privileges(role)
|
578
|
+
when ALLOWLIST
|
579
|
+
role.allowlist = parse_allowlist(len)
|
580
|
+
when READ_QUOTA
|
581
|
+
role.read_quota = @data_buffer.read_uint32(@data_offset)
|
582
|
+
@data_offset += len
|
583
|
+
when WRITE_QUOTA
|
584
|
+
role.write_quota = @data_buffer.read_uint32(@data_offset)
|
585
|
+
@data_offset += len
|
586
|
+
else
|
587
|
+
@data_offset += len
|
588
|
+
end
|
589
|
+
|
590
|
+
i = i.succ
|
591
|
+
end
|
592
|
+
|
593
|
+
next if role.name == "" && role.privileges == nil
|
594
|
+
|
595
|
+
role.privileges ||= []
|
596
|
+
list << role
|
597
|
+
end
|
598
|
+
|
599
|
+
return 0, list
|
600
|
+
end
|
601
|
+
|
602
|
+
def parse_privileges(role)
|
603
|
+
size = @data_buffer.read(@data_offset)
|
604
|
+
@data_offset += 1
|
605
|
+
role.privileges = []
|
606
|
+
|
607
|
+
i = 0
|
608
|
+
while i < size
|
609
|
+
priv = Privilege.new
|
610
|
+
priv.code = Privilege.from(@data_buffer.read(@data_offset))
|
611
|
+
@data_offset += 1
|
612
|
+
|
613
|
+
if priv.can_scope?
|
614
|
+
len = @data_buffer.read(@data_offset)
|
615
|
+
@data_offset += 1
|
616
|
+
priv.namespace = @data_buffer.read(@data_offset, len)
|
617
|
+
@data_offset += len
|
618
|
+
|
619
|
+
len = @data_buffer.read(@data_offset)
|
620
|
+
@data_offset += 1
|
621
|
+
priv.set_name = @data_buffer.read(@data_offset, len)
|
622
|
+
@data_offset += len
|
623
|
+
end
|
624
|
+
|
625
|
+
role.privileges << priv
|
626
|
+
|
627
|
+
i = i.succ
|
628
|
+
end
|
629
|
+
end
|
630
|
+
|
631
|
+
def parse_allowlist(len)
|
632
|
+
list = []
|
633
|
+
begn = @data_offset
|
634
|
+
max = begn + len
|
635
|
+
|
636
|
+
while @data_offset < max
|
637
|
+
if @data_buffer.read(@data_offset) == ','
|
638
|
+
l = @data_offset - begn
|
639
|
+
if l > 0
|
640
|
+
s = @data_buffer.read(begn, l)
|
641
|
+
list << s
|
642
|
+
end
|
643
|
+
@data_offset += 1
|
644
|
+
begn = @data_offset
|
645
|
+
else
|
646
|
+
@data_offset += 1
|
647
|
+
end
|
648
|
+
end
|
649
|
+
|
650
|
+
l = @data_offset - begn
|
651
|
+
if l > 0
|
652
|
+
s = @data_buffer.read(begn, l)
|
653
|
+
list << s
|
654
|
+
end
|
655
|
+
|
656
|
+
list
|
657
|
+
end
|
658
|
+
|
351
659
|
end
|
352
660
|
end
|
353
661
|
|
@@ -840,12 +840,6 @@ module Aerospike
|
|
840
840
|
end
|
841
841
|
|
842
842
|
def size_buffer_sz(size)
|
843
|
-
# Corrupted data streams can result in a hug.length.
|
844
|
-
# Do a sanity check here.
|
845
|
-
if size > Buffer::MAX_BUFFER_SIZE
|
846
|
-
raise Aerospike::Exceptions::Parse.new("Invalid size for buffer: #{size}")
|
847
|
-
end
|
848
|
-
|
849
843
|
@data_buffer.resize(size)
|
850
844
|
end
|
851
845
|
|
@@ -0,0 +1,164 @@
|
|
1
|
+
# encoding: utf-8
|
2
|
+
# Copyright 2014-2020 Aerospike, Inc.
|
3
|
+
#
|
4
|
+
# Portions may be licensed to Aerospike, Inc. under one or more contributor
|
5
|
+
# license agreements.
|
6
|
+
#
|
7
|
+
# Licensed under the Apache License, Version 2.0 (the "License"); you may not
|
8
|
+
# use this file except in compliance with the License. You may obtain a copy of
|
9
|
+
# the License at http:#www.apache.org/licenses/LICENSE-2.0
|
10
|
+
#
|
11
|
+
# Unless required by applicable law or agreed to in writing, software
|
12
|
+
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
|
13
|
+
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
|
14
|
+
# License for the specific language governing permissions and limitations under
|
15
|
+
# the License.
|
16
|
+
|
17
|
+
require 'aerospike/command/admin_command'
|
18
|
+
|
19
|
+
module Aerospike
|
20
|
+
|
21
|
+
private
|
22
|
+
|
23
|
+
attr_reader :session_token, :session_expiration
|
24
|
+
|
25
|
+
class LoginCommand < AdminCommand #:nodoc:
|
26
|
+
|
27
|
+
def login(conn, policy)
|
28
|
+
hashed_pass = LoginCommand.hash_password(policy.password)
|
29
|
+
authenticate(conn, policy, hashed_pass)
|
30
|
+
end
|
31
|
+
|
32
|
+
def authenticate(conn, user, hashed_pass)
|
33
|
+
write_header(LOGIN, 2)
|
34
|
+
write_field_str(USER, policy.user)
|
35
|
+
write_field_bytes(CREDENTIAL, hashed_pass)
|
36
|
+
|
37
|
+
parse_tokens(conn)
|
38
|
+
end
|
39
|
+
|
40
|
+
def authenticate_new(conn, cluster)
|
41
|
+
@data_offset = 8
|
42
|
+
policy = cluster.client_policy
|
43
|
+
case policy.auth_mode
|
44
|
+
when Aerospike::AuthMode::EXTERNAL
|
45
|
+
write_header(LOGIN, 3)
|
46
|
+
write_field_str(USER, policy.user)
|
47
|
+
write_field_bytes(CREDENTIAL, cluster.password)
|
48
|
+
write_field_str(CLEAR_PASSWORD, policy.password)
|
49
|
+
when Aerospike::AuthMode::INTERNAL
|
50
|
+
write_header(LOGIN, 2)
|
51
|
+
write_field_str(USER, policy.user)
|
52
|
+
write_field_bytes(CREDENTIAL, cluster.password)
|
53
|
+
when Aerospike::AuthMode::PKI
|
54
|
+
write_header(LOGIN, 0)
|
55
|
+
else
|
56
|
+
raise Exceptions::Aerospike.new(Aerospike::ResultCode::COMMAND_REJECTED, "Invalid client_policy#auth_mode.")
|
57
|
+
end
|
58
|
+
|
59
|
+
parse_tokens(conn)
|
60
|
+
cluster.session_token = @session_token
|
61
|
+
cluster.session_expiration = @session_expiration
|
62
|
+
end
|
63
|
+
|
64
|
+
def parse_tokens(conn)
|
65
|
+
begin
|
66
|
+
write_size
|
67
|
+
conn.write(@data_buffer, @data_offset)
|
68
|
+
conn.read(@data_buffer, HEADER_SIZE)
|
69
|
+
|
70
|
+
result = @data_buffer.read(RESULT_CODE)
|
71
|
+
|
72
|
+
if result != 0
|
73
|
+
return if result == Aerospike::ResultCode::SECURITY_NOT_ENABLED
|
74
|
+
raise Exceptions::Aerospike.new(result, "Authentication failed")
|
75
|
+
end
|
76
|
+
|
77
|
+
# read the rest of the buffer
|
78
|
+
size = @data_buffer.read_int64(0)
|
79
|
+
receive_size = (size & 0xFFFFFFFFFFFF) - HEADER_REMAINING
|
80
|
+
field_count = @data_buffer.read(11) & 0xFF
|
81
|
+
|
82
|
+
if receive_size <= 0 || receive_size > @data_buffer.size || field_count <= 0
|
83
|
+
raise Exceptions::Aerospike.new(result, "Node failed to retrieve session token")
|
84
|
+
end
|
85
|
+
|
86
|
+
if @data_buffer.size < receive_size
|
87
|
+
@data_buffer.resize(receive_size)
|
88
|
+
end
|
89
|
+
|
90
|
+
conn.read(@data_buffer, receive_size)
|
91
|
+
|
92
|
+
@data_offset = 0
|
93
|
+
for i in 0...field_count
|
94
|
+
mlen = @data_buffer.read_int32(@data_offset)
|
95
|
+
@data_offset += 4
|
96
|
+
id = @data_buffer.read(@data_offset)
|
97
|
+
@data_offset += 1
|
98
|
+
mlen -= 1
|
99
|
+
|
100
|
+
case id
|
101
|
+
when SESSION_TOKEN
|
102
|
+
# copy the contents of the buffer into a new byte slice
|
103
|
+
@session_token = @data_buffer.read(@data_offset, mlen)
|
104
|
+
|
105
|
+
when SESSION_TTL
|
106
|
+
# Subtract 60 seconds from TTL so client session expires before server session.
|
107
|
+
seconds = @data_buffer.read_int32(@data_offset) - 60
|
108
|
+
|
109
|
+
if seconds > 0
|
110
|
+
@session_expiration = Time.now + (seconds/86400)
|
111
|
+
else
|
112
|
+
Aerospike.logger.warn("Invalid session TTL: #{seconds}")
|
113
|
+
raise Exceptions::Aerospike.new(result, "Node failed to retrieve session token")
|
114
|
+
end
|
115
|
+
end
|
116
|
+
|
117
|
+
@data_offset += mlen
|
118
|
+
end
|
119
|
+
|
120
|
+
if !@session_token
|
121
|
+
raise Exceptions::Aerospike.new(result, "Node failed to retrieve session token")
|
122
|
+
end
|
123
|
+
ensure
|
124
|
+
Buffer.put(@data_buffer)
|
125
|
+
end
|
126
|
+
end
|
127
|
+
|
128
|
+
def authenticate_via_token(conn, cluster)
|
129
|
+
@data_offset = 8
|
130
|
+
policy = cluster.client_policy
|
131
|
+
if policy.auth_mode != Aerospike::AuthMode::PKI
|
132
|
+
write_header(AUTHENTICATE, 2)
|
133
|
+
write_field_str(USER, policy.user)
|
134
|
+
else
|
135
|
+
write_header(AUTHENTICATE, 1)
|
136
|
+
end
|
137
|
+
|
138
|
+
write_field_bytes(SESSION_TOKEN, cluster.session_token) if cluster.session_token
|
139
|
+
write_size
|
140
|
+
|
141
|
+
conn.write(@data_buffer, @data_offset)
|
142
|
+
conn.read(@data_buffer, HEADER_SIZE)
|
143
|
+
|
144
|
+
result = @data_buffer.read(RESULT_CODE)
|
145
|
+
size = @data_buffer.read_int64(0)
|
146
|
+
receive_size = (size & 0xFFFFFFFFFFFF) - HEADER_REMAINING
|
147
|
+
conn.read(@data_buffer, receive_size)
|
148
|
+
|
149
|
+
if result != 0
|
150
|
+
return if result == Aerospike::ResultCode::SECURITY_NOT_ENABLED
|
151
|
+
raise Exceptions::Aerospike.new(result, "Authentication failed")
|
152
|
+
end
|
153
|
+
|
154
|
+
nil
|
155
|
+
end
|
156
|
+
|
157
|
+
SALT = '$2a$10$7EqJtq98hPqEX7fNZaFWoO'
|
158
|
+
def self.hash_password(password)
|
159
|
+
# Hashing the password with the cost of 10, with a static salt
|
160
|
+
return BCrypt::Engine.hash_secret(password, SALT, :cost => 10)
|
161
|
+
end
|
162
|
+
end
|
163
|
+
end
|
164
|
+
|