Skip to content

Commit

Permalink
add support for ACL LOG command - issue #2170
Browse files Browse the repository at this point in the history
  • Loading branch information
tgrall authored and sazzad16 committed Dec 6, 2020
1 parent b54ce7d commit f32cd78
Show file tree
Hide file tree
Showing 10 changed files with 219 additions and 1 deletion.
17 changes: 17 additions & 0 deletions -
Original file line number Diff line number Diff line change
@@ -0,0 +1,17 @@
# Generated by CONFIG REWRITE
daemonize yes
protected-mode no
pidfile "/tmp/redis4.pid"

port 6382
logfile "/tmp/redis4.log"
user default on #1b58ee375b42e41f0e48ef2ff27d10a5b1f6924a9acdcdba7cae868e7adce6bf ~* +@all
dir "/Users/tgrall/projects/redis/clients/jedis"
requirepass 1b58ee375b42e41f0e48ef2ff27d10a5b1f6924a9acdcdba7cae868e7adce6bf

timeout 0

client-output-buffer-limit normal 0 0 0
masterauth "foobared"

replicaof 127.0.0.1 6381
12 changes: 12 additions & 0 deletions src/main/java/redis/clients/jedis/BinaryClient.java
Original file line number Diff line number Diff line change
Expand Up @@ -1323,6 +1323,18 @@ public void aclCat(final byte[] category) {
sendCommand(ACL, Keyword.CAT.raw, category);
}

public void aclLog() {
sendCommand(ACL, Keyword.LOG.raw);
}

public void aclLog(int limit) {
sendCommand(ACL, Keyword.LOG.raw, toByteArray(limit));
}

public void aclLog(final byte[] option) {
sendCommand(ACL, Keyword.LOG.raw, option);
}

public void aclSetUser(final byte[] name) {
sendCommand(ACL, Keyword.SETUSER.raw, name);
}
Expand Down
21 changes: 21 additions & 0 deletions src/main/java/redis/clients/jedis/BinaryJedis.java
Original file line number Diff line number Diff line change
Expand Up @@ -3747,6 +3747,27 @@ public List<byte[]> aclCat(byte[] category) {
return client.getBinaryMultiBulkReply();
}

@Override
public List<byte[]> aclLogBinary() {
checkIsInMultiOrPipeline();
client.aclLog();
return client.getBinaryMultiBulkReply();
}

@Override
public List<byte[]> aclLogBinary(final int limit) {
checkIsInMultiOrPipeline();
client.aclLog(limit);
return client.getBinaryMultiBulkReply();
}

@Override
public String aclLog(byte[] options) {
checkIsInMultiOrPipeline();
client.aclLog(options);
return client.getStatusCodeReply();
}

@Override
public String clientKill(final byte[] ipPort) {
checkIsInMultiOrPipeline();
Expand Down
46 changes: 46 additions & 0 deletions src/main/java/redis/clients/jedis/BuilderFactory.java
Original file line number Diff line number Diff line change
Expand Up @@ -522,6 +522,52 @@ public String toString() {

};

/**
* Create a list of Map that represent for example the ACL LOG return values
*/
public static final Builder<List<Map<String, String>>> LIST_STRING_MAP = new Builder<List<Map<String, String>>>() {

@Override
public List<Map<String, String>> build(Object data) {
if (null == data) {
return null;
}

List<ArrayList<byte[]>> objectList = (List<ArrayList<byte[]>>) data;
List<Map<String, String>> result = new ArrayList<>();

for (int i = 0; i < objectList.size() ; i++) {

final List<byte[]> flatHash = (List<byte[]>) objectList.get(i);
final Map<String, String> hash = new HashMap<>(flatHash.size()/2, 1);
final Iterator<byte[]> iterator = flatHash.iterator();
while (iterator.hasNext()) {
String key = SafeEncoder.encode(iterator.next());
String value = null;
Object o = iterator.next();
if ( o instanceof Long ) {
value = o.toString();
} else {
value = SafeEncoder.encode((byte[])o);
}
hash.put(key, value);
}
result.add(hash);
}


return result;

}

@Override
public String toString() {
return "List<Map<String, String>>";
}

};


public static final Builder<List<Long>> LONG_LIST = new Builder<List<Long>>() {
@Override
@SuppressWarnings("unchecked")
Expand Down
12 changes: 12 additions & 0 deletions src/main/java/redis/clients/jedis/Client.java
Original file line number Diff line number Diff line change
Expand Up @@ -1199,6 +1199,18 @@ public void aclCat(final String category) {
aclCat(SafeEncoder.encode(category));
}

public void aclLog() {
aclLog();
}

public void aclLog(final int limit) {
aclLog(toByteArray(limit));
}

public void aclLog(final String options) {
aclLog(SafeEncoder.encode(options));
}

public void aclDelUser(final String name) {
aclDelUser(SafeEncoder.encode(name));
}
Expand Down
18 changes: 18 additions & 0 deletions src/main/java/redis/clients/jedis/Jedis.java
Original file line number Diff line number Diff line change
Expand Up @@ -3772,6 +3772,24 @@ public List<String> aclCat(String category) {
return BuilderFactory.STRING_LIST.build(client.getObjectMultiBulkReply());
}

@Override
public List<Map<String,String>> aclLog() {
client.aclLog();
return BuilderFactory.LIST_STRING_MAP.build(client.getObjectMultiBulkReply());
}

@Override
public List<Map<String,String>> aclLog(int limit) {
client.aclLog(limit);
return BuilderFactory.LIST_STRING_MAP.build(client.getObjectMultiBulkReply());
}

@Override
public String aclLog(String options) {
client.aclLog(options);
return client.getStatusCodeReply();
}

@Override
public String aclGenPass() {
client.aclGenPass();
Expand Down
2 changes: 1 addition & 1 deletion src/main/java/redis/clients/jedis/Protocol.java
Original file line number Diff line number Diff line change
Expand Up @@ -281,7 +281,7 @@ public static enum Keyword {
GETNAME, SETNAME, LIST, MATCH, COUNT, PING, PONG, UNLOAD, REPLACE, KEYS, PAUSE, DOCTOR,
BLOCK, NOACK, STREAMS, KEY, CREATE, MKSTREAM, SETID, DESTROY, DELCONSUMER, MAXLEN, GROUP,
ID, IDLE, TIME, RETRYCOUNT, FORCE, USAGE, SAMPLES, STREAM, GROUPS, CONSUMERS, HELP, FREQ,
SETUSER, GETUSER, DELUSER, WHOAMI, CAT, GENPASS, USERS;
SETUSER, GETUSER, DELUSER, WHOAMI, CAT, GENPASS, USERS, LOG;

/**
* @deprecated This will be private in future. Use {@link #getRaw()}.
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,5 +74,11 @@ public interface AdvancedBinaryJedisCommands {

List<byte[]> aclCat(byte[] category);

List<byte[]> aclLogBinary();

List<byte[]> aclLogBinary(int limit);

String aclLog(byte[] options);

// TODO: Implements ACL LOAD/SAVE commands
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package redis.clients.jedis.commands;

import java.util.List;
import java.util.Map;

import redis.clients.jedis.AccessControlUser;
import redis.clients.jedis.params.MigrateParams;
Expand Down Expand Up @@ -74,5 +75,11 @@ public interface AdvancedJedisCommands {

List<String> aclCat(String category);

List<Map<String,String>> aclLog();

List<Map<String,String>> aclLog(int limit);

String aclLog(String options);

// TODO: Implements ACL LOAD/SAVE commands
}
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
import org.junit.*;
import redis.clients.jedis.AccessControlUser;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.Transaction;
import redis.clients.jedis.exceptions.JedisAccessControlException;
import redis.clients.jedis.tests.utils.RedisVersionUtil;

Expand Down Expand Up @@ -317,6 +318,84 @@ public void aclCatTest() {
}
}

@Test
public void aclLogTest() {
jedis.aclLog("RESET");
assertNotNull(jedis.aclLog()); // after reset the log should be null

// create new user and cconnect
jedis.aclSetUser("antirez", ">foo", "on", "+set", "~object:1234");
jedis.aclSetUser("antirez", "+eval", "+multi", "+exec");
jedis.auth("antirez", "foo");

// generate an error (antirez user does not have the permission to access foo)
try { jedis.get("foo"); } catch(JedisAccessControlException e) {}

// test the ACL Log
jedis.auth("default", "foobared");
assertEquals("Number of log messages ", 1, jedis.aclLog().size());
assertEquals("1", jedis.aclLog().get(0).get("count"));
assertEquals("antirez", jedis.aclLog().get(0).get("username"));
assertEquals("toplevel", jedis.aclLog().get(0).get("context"));
assertEquals("command", jedis.aclLog().get(0).get("reason"));
assertEquals("get", jedis.aclLog().get(0).get("object"));

// Capture similar event
jedis.aclLog("RESET");
assertNotNull(jedis.aclLog()); // after reset the log should be null
jedis.auth("antirez", "foo");

for(int i = 0; i < 10 ; i++ ) {
// generate an error (antirez user does not have the permission to access foo)
try { jedis.get("foo"); } catch (JedisAccessControlException e) {}
}

// test the ACL Log
jedis.auth("default", "foobared");
assertEquals("Number of log messages ", 1, jedis.aclLog().size());
assertEquals("10", jedis.aclLog().get(0).get("count"));
assertEquals("get", jedis.aclLog().get(0).get("object"));

// Generate another type of error
jedis.auth("antirez", "foo");
try { jedis.set("somekeynotallowed", "1234");} catch (JedisAccessControlException e) {}

// test the ACL Log
jedis.auth("default", "foobared");
assertEquals("Number of log messages ", 2, jedis.aclLog().size());
assertEquals("1", jedis.aclLog().get(0).get("count"));
assertEquals("somekeynotallowed", jedis.aclLog().get(0).get("object"));
assertEquals("key", jedis.aclLog().get(0).get("reason"));

jedis.aclLog("RESET");
assertNotNull(jedis.aclLog()); // after reset the log should be null

jedis.auth("antirez", "foo");
Transaction t = jedis.multi();
try{ t.incr("foo");} catch (Exception e){}
try{ t.exec();} catch (Exception e){}
t.close();

jedis.auth("default", "foobared");
assertEquals("Number of log messages ", 1, jedis.aclLog().size());
assertEquals("1", jedis.aclLog().get(0).get("count"));
assertEquals("multi", jedis.aclLog().get(0).get("context"));
assertEquals("incr", jedis.aclLog().get(0).get("object"));

// ACL LOG can accept a numerical argument to show less entries
jedis.auth("antirez", "foo");
for (int i = 0; i < 5; i++) {
try{ jedis.incr("foo");} catch (Exception e){}
}
try{ jedis.set("foo-2", "bar");} catch (Exception e){}

jedis.auth("default", "foobared");
assertEquals("Number of log messages ", 3, jedis.aclLog().size());
assertEquals("Number of log messages ", 2, jedis.aclLog(2).size());

jedis.aclDelUser("antirez");
}

@Test
public void aclGenPass() {
assertNotNull( jedis.aclGenPass() );
Expand Down

0 comments on commit f32cd78

Please sign in to comment.