Skip to content

jwt blacklist stop play/publish using jwt #4847

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 7 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions src/main/java/io/antmedia/AntMediaApplicationAdapter.java
Original file line number Diff line number Diff line change
Expand Up @@ -1509,6 +1509,8 @@ public static boolean updateAppSettingsFile(String appName, AppSettings newAppse
store.put(AppSettings.SETTINGS_PUBLISH_JWT_CONTROL_ENABLED, String.valueOf(newAppsettings.isPublishJwtControlEnabled()));
store.put(AppSettings.SETTINGS_PLAY_JWT_CONTROL_ENABLED, String.valueOf(newAppsettings.isPlayJwtControlEnabled()));
store.put(AppSettings.SETTINGS_JWT_STREAM_SECRET_KEY, newAppsettings.getJwtStreamSecretKey() != null ? newAppsettings.getJwtStreamSecretKey() : "");
store.put(AppSettings.SETTINGS_JWT_BLACKLIST_ENABLED, String.valueOf(newAppsettings.isJwtBlacklistEnabled()));


store.put(AppSettings.SETTINGS_WEBRTC_ENABLED, String.valueOf(newAppsettings.isWebRTCEnabled()));
store.put(AppSettings.SETTINGS_WEBRTC_FRAME_RATE, String.valueOf(newAppsettings.getWebRTCFrameRate()));
Expand Down
10 changes: 10 additions & 0 deletions src/main/java/io/antmedia/AppSettings.java
Original file line number Diff line number Diff line change
Expand Up @@ -267,6 +267,7 @@ public class AppSettings implements Serializable{
public static final String SETTINGS_JWT_SECRET_KEY = "settings.jwtSecretKey";

public static final String SETTINGS_JWT_CONTROL_ENABLED = "settings.jwtControlEnabled";
public static final String SETTINGS_JWT_BLACKLIST_ENABLED = "settings.jwtBlacklistEnabled";

public static final String SETTINGS_IP_FILTER_ENABLED = "settings.ipFilterEnabled";

Expand Down Expand Up @@ -1297,6 +1298,8 @@ public class AppSettings implements Serializable{
@Value( "${"+SETTINGS_JWT_CONTROL_ENABLED+":false}" )
private boolean jwtControlEnabled;

@Value( "${"+SETTINGS_JWT_BLACKLIST_ENABLED+":false}" )
private boolean jwtBlacklistEnabled;
/**
* Application IP Filter Enabled
*/
Expand Down Expand Up @@ -2668,6 +2671,13 @@ public boolean isJwtControlEnabled() {
return jwtControlEnabled;
}

public void setJwtBlacklistEnabled(boolean jwtBlacklistEnabled){
this.jwtBlacklistEnabled = jwtBlacklistEnabled;
}

public boolean isJwtBlacklistEnabled() {
return jwtBlacklistEnabled;
}
public void setJwtControlEnabled(boolean jwtControlEnabled) {
this.jwtControlEnabled = jwtControlEnabled;
}
Expand Down
38 changes: 37 additions & 1 deletion src/main/java/io/antmedia/datastore/db/DataStore.java
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,8 @@
import java.util.List;
import java.util.Map;

import io.antmedia.rest.model.Result;
import io.antmedia.security.ITokenService;
import org.apache.commons.lang3.RandomStringUtils;
import org.apache.commons.lang3.exception.ExceptionUtils;
import org.slf4j.Logger;
Expand Down Expand Up @@ -424,6 +426,29 @@ public List<Token> listAllTokens (Map<String, String> tokenMap, String streamId,

public abstract boolean deleteToken (String tokenId);

/**
* Whitelist specific token.
* @param tokenId id of the token
*/
public abstract boolean whiteListToken(String tokenId);

/**
* Get all blacklisted tokens.
*/
public abstract List<String> getBlackListedTokens();

/**
* Delete all blacklisted expired tokens.
*/
public abstract Result deleteAllBlacklistedExpiredTokens(ITokenService tokenService);

/**
* Whitelist all blacklisted tokens.
*
* @return
*/
public abstract boolean whiteListAllTokens();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The concept of allowing only people with valid tokens is implicitly a whitelist, so I think you should not give the impression that there is a separate whitelist and a blacklist. Is it not only clearing the blacklist..?


/**
* retrieve specific token
* @param tokenId id of the token
Expand Down Expand Up @@ -1364,7 +1389,18 @@ public List<WebRTCViewerInfo> getWebRTCViewerList(Map<String, String> webRTCView
* @param metaData new meta data
*/
public abstract boolean updateStreamMetaData(String streamId, String metaData);


/**
* Blacklist token.
* @param token which will be blacklisted.
*/
public abstract boolean blackListToken(Token token);

/**
* Get token from blacklist.
* @param tokenId id of the token.
*/
public abstract Token getBlackListedToken(String tokenId);

//**************************************
//ATTENTION: Write function descriptions while adding new functions
Expand Down
101 changes: 94 additions & 7 deletions src/main/java/io/antmedia/datastore/db/InMemoryDataStore.java
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,15 @@

import java.io.File;
import java.time.Instant;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;
import java.util.LinkedHashMap;
import java.util.List;
import java.util.Map;
import java.util.*;
import java.util.Map.Entry;
import java.util.Set;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.regex.Pattern;

import com.google.gson.Gson;
import com.google.gson.GsonBuilder;
import io.antmedia.rest.model.Result;
import io.antmedia.security.ITokenService;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.lang3.RandomStringUtils;
import org.apache.commons.lang3.exception.ExceptionUtils;
Expand Down Expand Up @@ -41,8 +41,11 @@ public class InMemoryDataStore extends DataStore {
private Map<String, ConferenceRoom> roomMap = new LinkedHashMap<>();
private Map<String, WebRTCViewerInfo> webRTCViewerMap = new LinkedHashMap<>();

private Gson gson;

public InMemoryDataStore(String dbName) {
GsonBuilder builder = new GsonBuilder();
gson = builder.create();
available = true;
}

Expand Down Expand Up @@ -900,6 +903,69 @@ public boolean deleteToken(String tokenId) {

}

@Override
public boolean whiteListToken(String tokenId) {
Token token = getToken(tokenId);
if(token != null && token.isBlackListed()){
token.setBlackListed(false);
return saveToken(token);
}


return false;
}

@Override
public List<String> getBlackListedTokens() {

ArrayList<String> tokenBlacklist = new ArrayList<>();
tokenMap.forEach((tokenId, token) -> {
if(token.isBlackListed()){
tokenBlacklist.add(tokenId);
}
});
return tokenBlacklist;

}

@Override
public Result deleteAllBlacklistedExpiredTokens(ITokenService tokenService) {
logger.info("Deleting all expired JWTs from token storage.");
AtomicInteger deletedTokenCount = new AtomicInteger();

tokenMap.forEach((tokenId, token) -> {
if(token.isBlackListed() && !tokenService.verifyJwt(tokenId,token.getStreamId(),token.getType())){
if(deleteToken(tokenId)){
deletedTokenCount.getAndIncrement();
}else{
logger.warn("Couldn't delete JWT:{}", tokenId);
}
}
});


if(deletedTokenCount.get() > 0){
final String successMsg = deletedTokenCount+" JWT deleted successfully from storage.";
logger.info(successMsg);
return new Result(true, successMsg);
}else{
final String failMsg = "No JWT deleted from storage.";
logger.warn(failMsg);
return new Result(false, failMsg);
}
}

@Override
public boolean whiteListAllTokens() {
tokenMap.forEach((tokenId, token) -> {
if(token.isBlackListed()){
whiteListToken(tokenId);
}
});

return true;
}

@Override
public Token getToken(String tokenId) {

Expand Down Expand Up @@ -1040,4 +1106,25 @@ public boolean updateStreamMetaData(String streamId, String metaData) {
}
return result;
}

@Override
public boolean blackListToken(Token token) {
boolean result = false;

if (token.getStreamId() != null && token.getTokenId() != null) {
token.setBlackListed(true);
return saveToken(token);
}

return result;
}

@Override
public Token getBlackListedToken(String tokenId) {
Token token = getToken(tokenId);
if(token != null && token.isBlackListed()){
return token;
}
return null;
}
}
100 changes: 100 additions & 0 deletions src/main/java/io/antmedia/datastore/db/MapBasedDataStore.java
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,11 @@
import java.util.Map;
import java.util.Map.Entry;
import java.util.Set;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.regex.Pattern;

import io.antmedia.rest.model.Result;
import io.antmedia.security.ITokenService;
import org.apache.commons.io.FilenameUtils;
import org.apache.commons.lang3.RandomStringUtils;
import org.apache.commons.lang3.exception.ExceptionUtils;
Expand Down Expand Up @@ -943,6 +946,79 @@ public boolean deleteToken(String tokenId) {
return result;
}

@Override
public boolean whiteListToken(String tokenId) {
synchronized (this){
Token token = getToken(tokenId);
if(token != null && token.isBlackListed()){
token.setBlackListed(false);
return saveToken(token);
}
}

return false;
}

@Override
public List<String> getBlackListedTokens(){
ArrayList<String> tokenBlacklist = new ArrayList<>();
synchronized (this){
tokenMap.forEach((tokenId, tokenAsJson) -> {
Token token = gson.fromJson(tokenAsJson,Token.class);
if(token.isBlackListed()){
tokenBlacklist.add(tokenId);
}
});
return tokenBlacklist;
}
}

@Override
public Result deleteAllBlacklistedExpiredTokens(ITokenService tokenService){
logger.info("Deleting all expired JWTs from token storage.");
AtomicInteger deletedTokenCount = new AtomicInteger();

synchronized (this) {

tokenMap.forEach((tokenId, tokenAsJson) -> {
Token token = gson.fromJson(tokenAsJson,Token.class);
if(token.isBlackListed() && !tokenService.verifyJwt(tokenId,token.getStreamId(),token.getType())){
if(deleteToken(tokenId)){
deletedTokenCount.getAndIncrement();
}else{
logger.warn("Couldn't delete JWT:{}", tokenId);
}
}
});
}

if(deletedTokenCount.get() > 0){
final String successMsg = deletedTokenCount+" JWT deleted successfully from storage.";
logger.info(successMsg);
return new Result(true, successMsg);
}else{
final String failMsg = "No JWT deleted from storage.";
logger.warn(failMsg);
return new Result(false, failMsg);
}

}

@Override
public boolean whiteListAllTokens(){

synchronized (this) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Using this as a synchronization point might not be a good choice as other classes can still hold a lock on this class. I would suggest using a non-public internal locking mechanism for synchronization purposes, which is more fine-grained and only applies to operations on the tokenMap, e.g. a ReadWriteLock or similar.

tokenMap.forEach((tokenId, tokenAsJson) -> {
Token token = gson.fromJson(tokenAsJson,Token.class);
if(token.isBlackListed()){
whiteListToken(tokenId);
}
});
}
return true;

}

@Override
public Token getToken(String tokenId) {
return super.getToken(tokenMap, tokenId, gson);
Expand Down Expand Up @@ -1074,4 +1150,28 @@ public Broadcast getBroadcastFromMap(String streamId)
return null;
}

@Override
public boolean blackListToken(Token token) {
boolean result = false;

synchronized (this) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'd revisit the usage of synchronized in this class, as there seems to be some overuse and overscoping. Using this as a monitor is not very fortunate, as it can be held externally. Here the monitor is obtained too early, only saveToken seems to be critical, but as far as I can see saveToken itself also takes care of synchronization using this, so this might be completely unnecessary.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

agree


if (token.getStreamId() != null && token.getTokenId() != null) {
token.setBlackListed(true);
return saveToken(token);
}
}
return result;

}

@Override
public Token getBlackListedToken(String tokenId) {
Token token = getToken(tokenId);
if(token != null && token.isBlackListed()){
return token;
}
return null;
}

}
11 changes: 5 additions & 6 deletions src/main/java/io/antmedia/datastore/db/MapDBStore.java
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,7 @@
import java.io.IOException;
import java.nio.file.Files;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Set;
import java.util.Map.Entry;

import org.apache.commons.lang3.exception.ExceptionUtils;
import org.mapdb.DB;
Expand All @@ -16,9 +13,7 @@
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import io.antmedia.datastore.db.types.Broadcast;
import io.antmedia.datastore.db.types.StreamInfo;
import io.antmedia.muxer.IAntMediaStreamHandler;
import io.vertx.core.Vertx;


Expand Down Expand Up @@ -73,6 +68,7 @@ public MapDBStore(String dbName, Vertx vertx) {
webRTCViewerMap = db.treeMap(WEBRTC_VIEWER).keySerializer(Serializer.STRING).valueSerializer(Serializer.STRING)
.counterEnable().createOrOpen();


timerId = vertx.setPeriodic(5000, id ->

vertx.executeBlocking(b -> {
Expand Down Expand Up @@ -124,7 +120,10 @@ public void close(boolean deleteDB) {
public void clearStreamInfoList(String streamId) {
//used in mongo for cluster mode. useless here.
}





@Override
public List<StreamInfo> getStreamInfoList(String streamId) {
return new ArrayList<>();
Expand Down
Loading