package com.janrain.backplane2.server.dao.redis;
import com.janrain.backplane2.server.BackplaneMessage;
import com.janrain.backplane.common.BackplaneServerException;
import com.janrain.backplane2.server.Grant;
import com.janrain.backplane2.server.Scope;
import com.janrain.backplane2.server.dao.GrantDAO;
import com.janrain.backplane2.server.dao.TokenDAO;
import com.janrain.oauth2.TokenException;
import com.janrain.redis.Redis;
import org.apache.commons.lang.SerializationUtils;
import org.apache.log4j.Logger;
import org.jetbrains.annotations.NotNull;
import redis.clients.jedis.Jedis;
import redis.clients.jedis.Transaction;
import java.util.ArrayList;
import java.util.List;
import java.util.Set;
/**
* @author Tom Raney
*/
public class RedisGrantDAO implements GrantDAO {
public RedisGrantDAO(TokenDAO tokenDao) {
this.tokenDAO = tokenDao;
}
@Override
public List<Grant> getByClientId(String clientId) throws BackplaneServerException {
List<Grant> grants = getAll();
List<Grant> filtered = new ArrayList<Grant>();
for (Grant grant: grants) {
if (clientId.equals(grant.get(Grant.GrantField.ISSUED_TO_CLIENT_ID)) &&
(grant.getState().isActive())) {
filtered.add(grant);
}
}
return filtered;
}
@Override
public void deleteByBuses(@NotNull List<String> busesToDelete) throws BackplaneServerException, TokenException {
Scope deleteBusesScope = new Scope(Scope.getEncodedScopesAsString(BackplaneMessage.Field.BUS, busesToDelete));
for(Grant grant : getAll()) {
Set<String> grantBuses = grant.getAuthorizedScope().getScopeFieldValues(BackplaneMessage.Field.BUS);
if (grantBuses == null) continue;
for(String bus : grantBuses) {
if (busesToDelete.contains(bus)) {
revokeBuses(grant, deleteBusesScope);
}
}
}
}
@Override
public boolean revokeBuses(List<Grant> grants, List<String> buses) throws BackplaneServerException, TokenException {
Scope busesToRevoke = new Scope(Scope.getEncodedScopesAsString(BackplaneMessage.Field.BUS, buses));
boolean changes = false;
for (Grant grant : grants) {
changes = revokeBuses(grant, busesToRevoke) || changes;
}
return changes;
}
@Override
public Grant get(String id) throws BackplaneServerException {
byte[] bytes = Redis.getInstance().get(getKey(id));
if (bytes != null) {
return (Grant) SerializationUtils.deserialize(bytes);
} else {
return null;
}
}
@Override
public List<Grant> getAll() throws BackplaneServerException {
List<byte[]> listOfBytes = Redis.getInstance().lrange(getKey("list"), 0, -1);
List<Grant> grants = new ArrayList<Grant>();
for (byte[] bytes : listOfBytes) {
if (bytes != null) {
grants.add((Grant) SerializationUtils.deserialize(bytes));
}
}
return grants;
}
@Override
public void persist(Grant obj) throws BackplaneServerException {
byte[] bytes = SerializationUtils.serialize(obj);
logger.info("adding grant " + obj.getIdValue() + " to redis");
// todo: one hash object instead of top level entries + a separate list?
Redis.getInstance().rpush(getKey("list"), bytes);
Redis.getInstance().set(getKey(obj.getIdValue()), bytes);
}
@Override
public void update(Grant existing, Grant updated) throws BackplaneServerException, TokenException {
Jedis jedis = null;
try {
tokenDAO.revokeTokenByGrant(existing.getIdValue());
jedis = Redis.getInstance().getWriteJedis();
byte[] newBytes = SerializationUtils.serialize(updated);
byte[] oldBytes = jedis.get(getKey(existing.getIdValue()));
Transaction t = jedis.multi();
t.lrem(getKey("list"), 0, oldBytes);
t.rpush(getKey("list"), newBytes);
t.set(getKey(updated.getIdValue()), newBytes);
t.exec();
logger.info("Updated grant (and revoked tokens): " + updated.getIdValue());
} finally {
Redis.getInstance().releaseToPool(jedis);
}
}
@Override
public void delete(String id) throws BackplaneServerException {
Jedis jedis = null;
try {
jedis = Redis.getInstance().getWriteJedis();
byte[] bytes = jedis.get(getKey(id));
if (bytes != null) {
if (jedis.lrem(getKey("list"), 0, bytes) == 0) {
logger.warn("failed to remove grant " + id + " from list " + new String(getKey("list")));
}
jedis.del(getKey(id));
}
tokenDAO.revokeTokenByGrant(id);
logger.info("deleted grant " + id);
} finally {
Redis.getInstance().releaseToPool(jedis);
}
}
// PRIVATE
private static final Logger logger = Logger.getLogger(RedisGrantDAO.class);
private final TokenDAO tokenDAO;
private static byte[] getKey(String id) {
return ("v2_grant_" + id).getBytes();
}
private boolean revokeBuses(Grant grant, Scope busesToRevoke) throws BackplaneServerException {
try {
Scope grantScope = grant.getAuthorizedScope();
Scope updatedScope = Scope.revoke(grantScope, busesToRevoke);
if (updatedScope.equals(grantScope)) return false;
if (!updatedScope.isAuthorizationRequired()) {
logger.info("Revoked all buses from grant: " + grant.getIdValue());
delete(grant.getIdValue());
} else {
Grant updated = new Grant.Builder(grant, grant.getState()).scope(updatedScope).buildGrant();
update(grant, updated);
logger.info("Buses updated updated for grant: " + updated.getIdValue() + " remaining scope: '" + updated.getAuthorizedScope() + "'");
}
return true;
} catch (Exception e) {
throw new BackplaneServerException(e.getMessage());
}
}
}