Skip to content
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

Fixed a bug that did not clear access rights when the password of private posts or categories was changed #1540

Merged
merged 4 commits into from
Nov 22, 2021
Merged
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
6 changes: 6 additions & 0 deletions src/main/java/run/halo/app/cache/CacheStore.java
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -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();
}
11 changes: 9 additions & 2 deletions src/main/java/run/halo/app/cache/InMemoryCacheStore.java
Original file line number Diff line number Diff line change
@@ -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;
Expand Down Expand Up @@ -58,7 +59,6 @@ void putInternal(@NonNull String key, @NonNull CacheWrapper<String> cacheWrapper

// Put the cache wrapper
CacheWrapper<String> putCacheWrapper = CACHE_CONTAINER.put(key, cacheWrapper);

log.debug("Put [{}] cache result: [{}], original cache wrapper: [{}]", key, putCacheWrapper,
cacheWrapper);
}
Expand Down Expand Up @@ -98,14 +98,21 @@ public void delete(@NonNull String key) {
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");
timer.cancel();
clear();
}

private void clear() {
public void clear() {
CACHE_CONTAINER.clear();
}

Expand Down
17 changes: 17 additions & 0 deletions src/main/java/run/halo/app/cache/LevelCacheStore.java
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@
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;
Expand Down Expand Up @@ -117,6 +118,22 @@ public void delete(@NonNull String key) {
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());
Expand Down
Original file line number Diff line number Diff line change
@@ -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) {
Expand Down Expand Up @@ -54,6 +63,29 @@ private void doDeleteAuthorization(String value) {
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) {
Expand All @@ -70,7 +102,7 @@ private String buildAccessPermissionKey() {

HttpServletRequest request = requestAttributes.getRequest();

return "ACCESS_PERMISSION: " + request.getSession().getId();
return ACCESS_PERMISSION_PREFIX + request.getSession().getId();
}

}
20 changes: 20 additions & 0 deletions src/test/java/run/halo/app/cache/InMemoryCacheStoreTest.java
Original file line number Diff line number Diff line change
Expand Up @@ -96,4 +96,24 @@ void deleteTest() {
// 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());
}
}
Original file line number Diff line number Diff line change
@@ -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;
}
}