未验证 提交 d965b6c3 编写于 作者: G guqing 提交者: GitHub

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 case
上级 9d6f53ee
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();
}
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());
......
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());
}
}
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;
}
}
Markdown is supported
0% .
You are about to add 0 people to the discussion. Proceed with caution.
先完成此消息的编辑!
想要评论请 注册