mirror of https://github.com/halo-dev/halo
fix: not clear access rights when the password of private posts or categories was changed (#1540)
* fix: 1518 * feat: add test case * fix: remove unused import clause * fix: unit test casepull/1549/head
parent
9d6f53eeaa
commit
d965b6c3e7
|
@ -1,5 +1,6 @@
|
|||
package run.halo.app.cache;
|
||||
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Optional;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import org.springframework.lang.NonNull;
|
||||
|
@ -61,4 +62,9 @@ public interface CacheStore<K, V> {
|
|||
*/
|
||||
void delete(@NonNull K key);
|
||||
|
||||
/**
|
||||
* Returns a view of the entries stored in this cache as a none thread-safe map.
|
||||
* Modifications made to the map do not directly affect the cache.
|
||||
*/
|
||||
LinkedHashMap<K, V> toMap();
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
package run.halo.app.cache;
|
||||
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Optional;
|
||||
import java.util.Timer;
|
||||
import java.util.TimerTask;
|
||||
|
@ -58,7 +59,6 @@ public class InMemoryCacheStore extends AbstractStringCacheStore {
|
|||
|
||||
// Put the cache wrapper
|
||||
CacheWrapper<String> putCacheWrapper = CACHE_CONTAINER.put(key, cacheWrapper);
|
||||
|
||||
log.debug("Put [{}] cache result: [{}], original cache wrapper: [{}]", key, putCacheWrapper,
|
||||
cacheWrapper);
|
||||
}
|
||||
|
@ -98,6 +98,13 @@ public class InMemoryCacheStore extends AbstractStringCacheStore {
|
|||
log.debug("Removed key: [{}]", key);
|
||||
}
|
||||
|
||||
@Override
|
||||
public LinkedHashMap<String, String> toMap() {
|
||||
LinkedHashMap<String, String> map = new LinkedHashMap<>();
|
||||
CACHE_CONTAINER.forEach((key, value) -> map.put(key, value.getData()));
|
||||
return map;
|
||||
}
|
||||
|
||||
@PreDestroy
|
||||
public void preDestroy() {
|
||||
log.debug("Cancelling all timer tasks");
|
||||
|
@ -105,7 +112,7 @@ public class InMemoryCacheStore extends AbstractStringCacheStore {
|
|||
clear();
|
||||
}
|
||||
|
||||
private void clear() {
|
||||
public void clear() {
|
||||
CACHE_CONTAINER.clear();
|
||||
}
|
||||
|
||||
|
|
|
@ -5,6 +5,7 @@ import java.io.File;
|
|||
import java.io.IOException;
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.Date;
|
||||
import java.util.LinkedHashMap;
|
||||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import java.util.Timer;
|
||||
|
@ -117,6 +118,22 @@ public class LevelCacheStore extends AbstractStringCacheStore {
|
|||
log.debug("cache remove key: [{}]", key);
|
||||
}
|
||||
|
||||
@Override
|
||||
public LinkedHashMap<String, String> toMap() {
|
||||
LinkedHashMap<String, String> map = new LinkedHashMap<>();
|
||||
LEVEL_DB.forEach(entry -> {
|
||||
String key = bytesToString(entry.getKey());
|
||||
String valueJson = bytesToString(entry.getValue());
|
||||
Optional<CacheWrapper<String>> cacheWrapperOptional = jsonToCacheWrapper(valueJson);
|
||||
if (cacheWrapperOptional.isPresent()) {
|
||||
map.put(key, cacheWrapperOptional.get().getData());
|
||||
} else {
|
||||
map.put(key, null);
|
||||
}
|
||||
});
|
||||
return map;
|
||||
}
|
||||
|
||||
|
||||
private byte[] stringToBytes(String str) {
|
||||
return str.getBytes(Charset.defaultCharset());
|
||||
|
|
|
@ -1,22 +1,31 @@
|
|||
package run.halo.app.service.impl;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.core.type.TypeReference;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedHashSet;
|
||||
import java.util.Map.Entry;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import javax.servlet.http.HttpServletRequest;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.springframework.stereotype.Service;
|
||||
import org.springframework.web.context.request.RequestContextHolder;
|
||||
import org.springframework.web.context.request.ServletRequestAttributes;
|
||||
import run.halo.app.cache.AbstractStringCacheStore;
|
||||
import run.halo.app.service.AuthorizationService;
|
||||
import run.halo.app.utils.JsonUtils;
|
||||
|
||||
/**
|
||||
* @author ZhiXiang Yuan
|
||||
* @author guqing
|
||||
* @date 2021/01/21 11:28
|
||||
*/
|
||||
@Slf4j
|
||||
@Service
|
||||
public class AuthorizationServiceImpl implements AuthorizationService {
|
||||
|
||||
private static final String ACCESS_PERMISSION_PREFIX = "ACCESS_PERMISSION: ";
|
||||
private final AbstractStringCacheStore cacheStore;
|
||||
|
||||
public AuthorizationServiceImpl(AbstractStringCacheStore cacheStore) {
|
||||
|
@ -54,6 +63,29 @@ public class AuthorizationServiceImpl implements AuthorizationService {
|
|||
accessStore.remove(value);
|
||||
|
||||
cacheStore.putAny(buildAccessPermissionKey(), accessStore, 1, TimeUnit.DAYS);
|
||||
|
||||
for (Entry<String, String> entry : cacheStore.toMap().entrySet()) {
|
||||
String key = entry.getKey();
|
||||
if (!key.startsWith(ACCESS_PERMISSION_PREFIX)) {
|
||||
continue;
|
||||
}
|
||||
Set<String> valueSet = jsonToValueSet(entry.getValue());
|
||||
if (valueSet.contains(value)) {
|
||||
valueSet.remove(value);
|
||||
cacheStore.putAny(key, valueSet, 1, TimeUnit.DAYS);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private Set<String> jsonToValueSet(String json) {
|
||||
try {
|
||||
return JsonUtils.DEFAULT_JSON_MAPPER.readValue(json,
|
||||
new TypeReference<LinkedHashSet<String>>() {
|
||||
});
|
||||
} catch (JsonProcessingException e) {
|
||||
log.warn("Failed to convert json to authorization cache value set: [{}]", json, e);
|
||||
}
|
||||
return Collections.emptySet();
|
||||
}
|
||||
|
||||
private void doAuthorization(String value) {
|
||||
|
@ -70,7 +102,7 @@ public class AuthorizationServiceImpl implements AuthorizationService {
|
|||
|
||||
HttpServletRequest request = requestAttributes.getRequest();
|
||||
|
||||
return "ACCESS_PERMISSION: " + request.getSession().getId();
|
||||
return ACCESS_PERMISSION_PREFIX + request.getSession().getId();
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -96,4 +96,24 @@ class InMemoryCacheStoreTest {
|
|||
// Assertion
|
||||
assertFalse(valueOptional.isPresent());
|
||||
}
|
||||
|
||||
@Test
|
||||
void toMapTest() {
|
||||
InMemoryCacheStore localCacheStore = new InMemoryCacheStore();
|
||||
localCacheStore.clear();
|
||||
String key1 = "test_key_1";
|
||||
String value1 = "test_value_1";
|
||||
|
||||
// Put the cache
|
||||
localCacheStore.put(key1, value1);
|
||||
assertEquals("{test_key_1=test_value_1}", localCacheStore.toMap().toString());
|
||||
|
||||
String key2 = "test_key_2";
|
||||
String value2 = "test_value_2";
|
||||
|
||||
// Put the cache
|
||||
localCacheStore.put(key2, value2);
|
||||
assertEquals("{test_key_2=test_value_2, test_key_1=test_value_1}",
|
||||
localCacheStore.toMap().toString());
|
||||
}
|
||||
}
|
||||
|
|
|
@ -0,0 +1,129 @@
|
|||
package run.halo.app.service.impl;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import java.util.Set;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.mock.web.MockHttpServletRequest;
|
||||
import org.springframework.mock.web.MockHttpSession;
|
||||
import org.springframework.mock.web.MockServletContext;
|
||||
import org.springframework.web.context.request.RequestContextHolder;
|
||||
import org.springframework.web.context.request.ServletRequestAttributes;
|
||||
import run.halo.app.cache.InMemoryCacheStore;
|
||||
import run.halo.app.utils.JsonUtils;
|
||||
|
||||
/**
|
||||
* @author guqing
|
||||
* @date 2021-11-19
|
||||
*/
|
||||
public class AuthorizationServiceImplTest {
|
||||
|
||||
private AuthorizationServiceImpl authorizationService;
|
||||
private InMemoryCacheStore inMemoryCacheStore;
|
||||
|
||||
@BeforeEach
|
||||
public void setUp() {
|
||||
inMemoryCacheStore = new InMemoryCacheStore();
|
||||
authorizationService = new AuthorizationServiceImpl(inMemoryCacheStore);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void deletePostAuthorizationTest() {
|
||||
inMemoryCacheStore.clear();
|
||||
RequestContextHolder.setRequestAttributes(mockRequestAttributes("1"));
|
||||
|
||||
authorizationService.postAuthorization(1);
|
||||
authorizationService.postAuthorization(2);
|
||||
|
||||
Set<String> permissions = authorizationService.getAccessPermissionStore();
|
||||
assertEquals("[POST:1, POST:2]", permissions.toString());
|
||||
|
||||
authorizationService.deletePostAuthorization(1);
|
||||
Set<String> permissionsAfterDelete = authorizationService.getAccessPermissionStore();
|
||||
assertEquals("[POST:2]", permissionsAfterDelete.toString());
|
||||
|
||||
RequestContextHolder.resetRequestAttributes();
|
||||
inMemoryCacheStore.clear();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void complexityOfDeletePostAuthorizationTest() {
|
||||
inMemoryCacheStore.clear();
|
||||
// simulate session of user 1
|
||||
RequestContextHolder.setRequestAttributes(mockRequestAttributes("1"));
|
||||
// user 1 accessed two encrypted posts
|
||||
authorizationService.postAuthorization(1);
|
||||
authorizationService.postAuthorization(2);
|
||||
|
||||
// simulate session of user 2
|
||||
RequestContextHolder.setRequestAttributes(mockRequestAttributes("2"));
|
||||
|
||||
// user 2 accessed two encrypted posts
|
||||
authorizationService.postAuthorization(2);
|
||||
authorizationService.postAuthorization(3);
|
||||
|
||||
assertEquals(objectToJson(inMemoryCacheStore.toMap()),
|
||||
"{\"ACCESS_PERMISSION: 2\":\"[\\\"POST:3\\\",\\\"POST:2\\\"]\","
|
||||
+ "\"ACCESS_PERMISSION: 1\":\"[\\\"POST:1\\\",\\\"POST:2\\\"]\"}");
|
||||
|
||||
// simulate the admin user to change the post password
|
||||
authorizationService.deletePostAuthorization(2);
|
||||
|
||||
assertEquals(objectToJson(inMemoryCacheStore.toMap()),
|
||||
"{\"ACCESS_PERMISSION: 2\":\"[\\\"POST:3\\\"]\","
|
||||
+ "\"ACCESS_PERMISSION: 1\":\"[\\\"POST:1\\\"]\"}");
|
||||
|
||||
RequestContextHolder.resetRequestAttributes();
|
||||
inMemoryCacheStore.clear();
|
||||
}
|
||||
|
||||
@Test
|
||||
public void deleteCategoryAuthorizationTest() {
|
||||
inMemoryCacheStore.clear();
|
||||
// simulate session of user 1
|
||||
RequestContextHolder.setRequestAttributes(mockRequestAttributes("1"));
|
||||
// user 1 accessed two encrypted posts
|
||||
authorizationService.categoryAuthorization(1);
|
||||
authorizationService.categoryAuthorization(2);
|
||||
|
||||
// simulate session of user 2
|
||||
RequestContextHolder.setRequestAttributes(mockRequestAttributes("2"));
|
||||
// user 2 accessed two encrypted categories
|
||||
authorizationService.categoryAuthorization(1);
|
||||
authorizationService.categoryAuthorization(3);
|
||||
|
||||
assertEquals(objectToJson(inMemoryCacheStore.toMap()),
|
||||
"{\"ACCESS_PERMISSION: 2\":\"[\\\"CATEGORY:1\\\",\\\"CATEGORY:3\\\"]\","
|
||||
+ "\"ACCESS_PERMISSION: 1\":\"[\\\"CATEGORY:1\\\",\\\"CATEGORY:2\\\"]\"}");
|
||||
|
||||
// simulate the admin user to change the category password of No.1
|
||||
authorizationService.deleteCategoryAuthorization(1);
|
||||
|
||||
assertEquals(objectToJson(inMemoryCacheStore.toMap()),
|
||||
"{\"ACCESS_PERMISSION: 2\":\"[\\\"CATEGORY:3\\\"]\","
|
||||
+ "\"ACCESS_PERMISSION: 1\":\"[\\\"CATEGORY:2\\\"]\"}");
|
||||
|
||||
RequestContextHolder.resetRequestAttributes();
|
||||
inMemoryCacheStore.clear();
|
||||
}
|
||||
|
||||
private ServletRequestAttributes mockRequestAttributes(String sessionId) {
|
||||
MockHttpServletRequest request = new MockHttpServletRequest();
|
||||
MockServletContext context = new MockServletContext();
|
||||
MockHttpSession session = new MockHttpSession(context, sessionId);
|
||||
request.setSession(session);
|
||||
return new ServletRequestAttributes(request);
|
||||
}
|
||||
|
||||
private String objectToJson(Object o) {
|
||||
try {
|
||||
return JsonUtils.objectToJson(o);
|
||||
} catch (JsonProcessingException e) {
|
||||
// ignore this
|
||||
}
|
||||
return StringUtils.EMPTY;
|
||||
}
|
||||
}
|
Loading…
Reference in New Issue