mirror of https://github.com/halo-dev/halo
Fix backup download error and add temp token support
parent
5a0eac4722
commit
60cac1313e
|
@ -51,7 +51,8 @@ public class HaloConfiguration {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
public RestTemplate httpsRestTemplate(RestTemplateBuilder builder) throws NoSuchAlgorithmException, KeyStoreException, KeyManagementException {
|
public RestTemplate httpsRestTemplate(RestTemplateBuilder builder)
|
||||||
|
throws NoSuchAlgorithmException, KeyStoreException, KeyManagementException {
|
||||||
RestTemplate httpsRestTemplate = builder.build();
|
RestTemplate httpsRestTemplate = builder.build();
|
||||||
httpsRestTemplate.setRequestFactory(new HttpComponentsClientHttpRequestFactory(HttpClientUtils.createHttpsClient(
|
httpsRestTemplate.setRequestFactory(new HttpComponentsClientHttpRequestFactory(HttpClientUtils.createHttpsClient(
|
||||||
(int) haloProperties.getDownloadTimeout().toMillis())));
|
(int) haloProperties.getDownloadTimeout().toMillis())));
|
||||||
|
@ -97,8 +98,9 @@ public class HaloConfiguration {
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
public FilterRegistrationBean<ContentFilter> contentFilter(HaloProperties haloProperties,
|
public FilterRegistrationBean<ContentFilter> contentFilter(HaloProperties haloProperties,
|
||||||
OptionService optionService) {
|
OptionService optionService,
|
||||||
ContentFilter contentFilter = new ContentFilter(haloProperties, optionService);
|
StringCacheStore cacheStore) {
|
||||||
|
ContentFilter contentFilter = new ContentFilter(haloProperties, optionService, cacheStore);
|
||||||
contentFilter.setFailureHandler(new ContentAuthenticationFailureHandler());
|
contentFilter.setFailureHandler(new ContentAuthenticationFailureHandler());
|
||||||
contentFilter.addExcludeUrlPatterns("/api/**", "/install", "/version", "/admin/**", "/js/**", "/css/**");
|
contentFilter.addExcludeUrlPatterns("/api/**", "/install", "/version", "/admin/**", "/js/**", "/css/**");
|
||||||
|
|
||||||
|
@ -113,8 +115,9 @@ public class HaloConfiguration {
|
||||||
@Bean
|
@Bean
|
||||||
public FilterRegistrationBean<ApiAuthenticationFilter> apiAuthenticationFilter(HaloProperties haloProperties,
|
public FilterRegistrationBean<ApiAuthenticationFilter> apiAuthenticationFilter(HaloProperties haloProperties,
|
||||||
ObjectMapper objectMapper,
|
ObjectMapper objectMapper,
|
||||||
OptionService optionService) {
|
OptionService optionService,
|
||||||
ApiAuthenticationFilter apiFilter = new ApiAuthenticationFilter(haloProperties, optionService);
|
StringCacheStore cacheStore) {
|
||||||
|
ApiAuthenticationFilter apiFilter = new ApiAuthenticationFilter(haloProperties, optionService, cacheStore);
|
||||||
apiFilter.addExcludeUrlPatterns(
|
apiFilter.addExcludeUrlPatterns(
|
||||||
"/api/content/*/comments",
|
"/api/content/*/comments",
|
||||||
"/api/content/**/comments/**",
|
"/api/content/**/comments/**",
|
||||||
|
|
|
@ -13,6 +13,7 @@ import org.springframework.util.Assert;
|
||||||
import org.springframework.web.bind.annotation.RequestMethod;
|
import org.springframework.web.bind.annotation.RequestMethod;
|
||||||
import run.halo.app.config.properties.HaloProperties;
|
import run.halo.app.config.properties.HaloProperties;
|
||||||
import run.halo.app.model.entity.User;
|
import run.halo.app.model.entity.User;
|
||||||
|
import run.halo.app.model.support.HaloConst;
|
||||||
import run.halo.app.security.filter.AdminAuthenticationFilter;
|
import run.halo.app.security.filter.AdminAuthenticationFilter;
|
||||||
import run.halo.app.security.filter.ApiAuthenticationFilter;
|
import run.halo.app.security.filter.ApiAuthenticationFilter;
|
||||||
import run.halo.app.security.support.UserDetail;
|
import run.halo.app.security.support.UserDetail;
|
||||||
|
@ -33,7 +34,7 @@ import java.util.Arrays;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
import static run.halo.app.model.support.HaloConst.HALO_VERSION;
|
import static run.halo.app.model.support.HaloConst.*;
|
||||||
import static springfox.documentation.schema.AlternateTypeRules.newRule;
|
import static springfox.documentation.schema.AlternateTypeRules.newRule;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -123,8 +124,8 @@ public class SwaggerConfiguration {
|
||||||
|
|
||||||
private List<ApiKey> adminApiKeys() {
|
private List<ApiKey> adminApiKeys() {
|
||||||
return Arrays.asList(
|
return Arrays.asList(
|
||||||
new ApiKey("Token from header", AdminAuthenticationFilter.ADMIN_TOKEN_HEADER_NAME, In.HEADER.name()),
|
new ApiKey("Token from header", ADMIN_TOKEN_HEADER_NAME, In.HEADER.name()),
|
||||||
new ApiKey("Token from query", AdminAuthenticationFilter.ADMIN_TOKEN_QUERY_NAME, In.QUERY.name())
|
new ApiKey("Token from query", ADMIN_TOKEN_QUERY_NAME, In.QUERY.name())
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -139,8 +140,8 @@ public class SwaggerConfiguration {
|
||||||
|
|
||||||
private List<ApiKey> contentApiKeys() {
|
private List<ApiKey> contentApiKeys() {
|
||||||
return Arrays.asList(
|
return Arrays.asList(
|
||||||
new ApiKey("Access key from header", ApiAuthenticationFilter.API_ACCESS_KEY_HEADER_NAME, In.HEADER.name()),
|
new ApiKey("Access key from header", API_ACCESS_KEY_HEADER_NAME, In.HEADER.name()),
|
||||||
new ApiKey("Access key from query", ApiAuthenticationFilter.API_ACCESS_KEY_QUERY_NAME, In.QUERY.name())
|
new ApiKey("Access key from query", API_ACCESS_KEY_QUERY_NAME, In.QUERY.name())
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -14,6 +14,9 @@ import javax.servlet.http.HttpServletRequest;
|
||||||
import javax.servlet.http.HttpServletResponse;
|
import javax.servlet.http.HttpServletResponse;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
|
||||||
|
import static run.halo.app.model.support.HaloConst.ADMIN_TOKEN_HEADER_NAME;
|
||||||
|
import static run.halo.app.model.support.HaloConst.API_ACCESS_KEY_HEADER_NAME;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Filter for CORS.
|
* Filter for CORS.
|
||||||
*
|
*
|
||||||
|
@ -21,7 +24,7 @@ import java.io.IOException;
|
||||||
*/
|
*/
|
||||||
public class CorsFilter extends GenericFilterBean {
|
public class CorsFilter extends GenericFilterBean {
|
||||||
|
|
||||||
private final static String ALLOW_HEADERS = StringUtils.joinWith(",", HttpHeaders.CONTENT_TYPE, AdminAuthenticationFilter.ADMIN_TOKEN_HEADER_NAME);
|
private final static String ALLOW_HEADERS = StringUtils.joinWith(",", HttpHeaders.CONTENT_TYPE, ADMIN_TOKEN_HEADER_NAME, API_ACCESS_KEY_HEADER_NAME);
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
|
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain) throws IOException, ServletException {
|
||||||
|
|
|
@ -1,6 +1,9 @@
|
||||||
package run.halo.app.model.support;
|
package run.halo.app.model.support;
|
||||||
|
|
||||||
|
import org.springframework.http.HttpHeaders;
|
||||||
|
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
|
import java.time.Duration;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* <pre>
|
* <pre>
|
||||||
|
@ -106,6 +109,33 @@ public class HaloConst {
|
||||||
|
|
||||||
public final static String HALO_ADMIN_RELATIVE_BACKUP_PATH = "templates/admin-backup/";
|
public final static String HALO_ADMIN_RELATIVE_BACKUP_PATH = "templates/admin-backup/";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Content token header name.
|
||||||
|
*/
|
||||||
|
public final static String API_ACCESS_KEY_HEADER_NAME = "API-" + HttpHeaders.AUTHORIZATION;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Admin token header name.
|
||||||
|
*/
|
||||||
|
public final static String ADMIN_TOKEN_HEADER_NAME = "ADMIN-" + HttpHeaders.AUTHORIZATION;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Admin token param name.
|
||||||
|
*/
|
||||||
|
public final static String ADMIN_TOKEN_QUERY_NAME = "admin_token";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Temporary token.
|
||||||
|
*/
|
||||||
|
public final static String TEMP_TOKEN = "temp_token";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Content api token param name
|
||||||
|
*/
|
||||||
|
public final static String API_ACCESS_KEY_QUERY_NAME = "api_access_key";
|
||||||
|
|
||||||
|
public final static Duration TEMP_TOKEN_EXPIRATION = Duration.ofDays(7);
|
||||||
|
|
||||||
static {
|
static {
|
||||||
// Set version
|
// Set version
|
||||||
HALO_VERSION = HaloConst.class.getPackage().getImplementationVersion();
|
HALO_VERSION = HaloConst.class.getPackage().getImplementationVersion();
|
||||||
|
|
|
@ -1,16 +1,21 @@
|
||||||
package run.halo.app.security.filter;
|
package run.halo.app.security.filter;
|
||||||
|
|
||||||
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
import org.apache.commons.lang3.StringUtils;
|
||||||
import org.springframework.lang.NonNull;
|
import org.springframework.lang.NonNull;
|
||||||
import org.springframework.lang.Nullable;
|
import org.springframework.lang.Nullable;
|
||||||
import org.springframework.util.AntPathMatcher;
|
import org.springframework.util.AntPathMatcher;
|
||||||
import org.springframework.util.Assert;
|
import org.springframework.util.Assert;
|
||||||
import org.springframework.web.filter.OncePerRequestFilter;
|
import org.springframework.web.filter.OncePerRequestFilter;
|
||||||
|
import run.halo.app.cache.StringCacheStore;
|
||||||
import run.halo.app.config.properties.HaloProperties;
|
import run.halo.app.config.properties.HaloProperties;
|
||||||
import run.halo.app.exception.NotInstallException;
|
import run.halo.app.exception.NotInstallException;
|
||||||
import run.halo.app.model.properties.PrimaryProperties;
|
import run.halo.app.model.properties.PrimaryProperties;
|
||||||
|
import run.halo.app.model.support.HaloConst;
|
||||||
import run.halo.app.security.context.SecurityContextHolder;
|
import run.halo.app.security.context.SecurityContextHolder;
|
||||||
import run.halo.app.security.handler.AuthenticationFailureHandler;
|
import run.halo.app.security.handler.AuthenticationFailureHandler;
|
||||||
import run.halo.app.security.handler.DefaultAuthenticationFailureHandler;
|
import run.halo.app.security.handler.DefaultAuthenticationFailureHandler;
|
||||||
|
import run.halo.app.security.util.SecurityUtils;
|
||||||
import run.halo.app.service.OptionService;
|
import run.halo.app.service.OptionService;
|
||||||
|
|
||||||
import javax.servlet.FilterChain;
|
import javax.servlet.FilterChain;
|
||||||
|
@ -18,10 +23,7 @@ import javax.servlet.ServletException;
|
||||||
import javax.servlet.http.HttpServletRequest;
|
import javax.servlet.http.HttpServletRequest;
|
||||||
import javax.servlet.http.HttpServletResponse;
|
import javax.servlet.http.HttpServletResponse;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.Collection;
|
import java.util.*;
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.HashSet;
|
|
||||||
import java.util.Set;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Abstract authentication filter.
|
* Abstract authentication filter.
|
||||||
|
@ -29,21 +31,29 @@ import java.util.Set;
|
||||||
* @author johnniang
|
* @author johnniang
|
||||||
* @date 19-4-16
|
* @date 19-4-16
|
||||||
*/
|
*/
|
||||||
|
@Slf4j
|
||||||
public abstract class AbstractAuthenticationFilter extends OncePerRequestFilter {
|
public abstract class AbstractAuthenticationFilter extends OncePerRequestFilter {
|
||||||
|
|
||||||
protected final AntPathMatcher antPathMatcher;
|
protected final AntPathMatcher antPathMatcher;
|
||||||
|
|
||||||
protected final HaloProperties haloProperties;
|
protected final HaloProperties haloProperties;
|
||||||
|
|
||||||
protected final OptionService optionService;
|
protected final OptionService optionService;
|
||||||
|
|
||||||
|
protected final StringCacheStore cacheStore;
|
||||||
|
|
||||||
private AuthenticationFailureHandler failureHandler;
|
private AuthenticationFailureHandler failureHandler;
|
||||||
/**
|
/**
|
||||||
* Exclude url patterns.
|
* Exclude url patterns.
|
||||||
*/
|
*/
|
||||||
private Set<String> excludeUrlPatterns = new HashSet<>(2);
|
private Set<String> excludeUrlPatterns = new HashSet<>(2);
|
||||||
|
|
||||||
protected AbstractAuthenticationFilter(HaloProperties haloProperties,
|
AbstractAuthenticationFilter(HaloProperties haloProperties,
|
||||||
OptionService optionService) {
|
OptionService optionService,
|
||||||
|
StringCacheStore cacheStore) {
|
||||||
this.haloProperties = haloProperties;
|
this.haloProperties = haloProperties;
|
||||||
this.optionService = optionService;
|
this.optionService = optionService;
|
||||||
|
this.cacheStore = cacheStore;
|
||||||
|
|
||||||
antPathMatcher = new AntPathMatcher();
|
antPathMatcher = new AntPathMatcher();
|
||||||
}
|
}
|
||||||
|
@ -146,6 +156,11 @@ public abstract class AbstractAuthenticationFilter extends OncePerRequestFilter
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (checkForTempToken(request)) {
|
||||||
|
filterChain.doFilter(request, response);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
try {
|
try {
|
||||||
// Do authenticate
|
// Do authenticate
|
||||||
doAuthenticate(request, response, filterChain);
|
doAuthenticate(request, response, filterChain);
|
||||||
|
@ -154,4 +169,58 @@ public abstract class AbstractAuthenticationFilter extends OncePerRequestFilter
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private boolean checkForTempToken(HttpServletRequest request) {
|
||||||
|
// Get token from request
|
||||||
|
String tempToken = getTokenFromRequest(request, HaloConst.TEMP_TOKEN, HaloConst.TEMP_TOKEN);
|
||||||
|
|
||||||
|
if (StringUtils.isEmpty(tempToken)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
String tempTokenKey = SecurityUtils.buildTempTokenKey(tempToken);
|
||||||
|
// Check the token
|
||||||
|
Optional<Integer> tokenCountOptional = cacheStore.getAny(tempTokenKey, Integer.class);
|
||||||
|
|
||||||
|
if (!tokenCountOptional.isPresent()) {
|
||||||
|
// If the token is not found
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
log.info("Got valid temp token: [{}]", tempToken);
|
||||||
|
|
||||||
|
int count = tokenCountOptional.get();
|
||||||
|
// TODO May cause unsafe thread, fixing next time
|
||||||
|
// Count down
|
||||||
|
count--;
|
||||||
|
if (count <= 0) {
|
||||||
|
// If count is less than 0, then clear this temp token
|
||||||
|
cacheStore.delete(tempTokenKey);
|
||||||
|
} else {
|
||||||
|
// Put the less count
|
||||||
|
cacheStore.put(tempTokenKey, String.valueOf(count));
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
String getTokenFromRequest(@NonNull HttpServletRequest request, @NonNull String tokenQueryName, @NonNull String tokenHeaderName) {
|
||||||
|
Assert.notNull(request, "Http servlet request must not be null");
|
||||||
|
Assert.hasText(tokenQueryName, "Token query name must not be blank");
|
||||||
|
Assert.hasText(tokenHeaderName, "Token header name must not be blank");
|
||||||
|
|
||||||
|
// Get from header
|
||||||
|
String accessKey = request.getHeader(tokenHeaderName);
|
||||||
|
|
||||||
|
// Get from param
|
||||||
|
if (StringUtils.isBlank(accessKey)) {
|
||||||
|
accessKey = request.getParameter(tokenQueryName);
|
||||||
|
|
||||||
|
log.debug("Got access key from parameter: [{}: {}]", tokenQueryName, accessKey);
|
||||||
|
} else {
|
||||||
|
log.debug("Got access key from header: [{}: {}]", tokenHeaderName, accessKey);
|
||||||
|
}
|
||||||
|
|
||||||
|
return accessKey;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,9 +2,7 @@ package run.halo.app.security.filter;
|
||||||
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
import org.springframework.http.HttpHeaders;
|
|
||||||
import org.springframework.lang.NonNull;
|
import org.springframework.lang.NonNull;
|
||||||
import org.springframework.util.Assert;
|
|
||||||
import run.halo.app.cache.StringCacheStore;
|
import run.halo.app.cache.StringCacheStore;
|
||||||
import run.halo.app.config.properties.HaloProperties;
|
import run.halo.app.config.properties.HaloProperties;
|
||||||
import run.halo.app.exception.AuthenticationException;
|
import run.halo.app.exception.AuthenticationException;
|
||||||
|
@ -24,6 +22,9 @@ import javax.servlet.http.HttpServletResponse;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
|
||||||
|
import static run.halo.app.model.support.HaloConst.ADMIN_TOKEN_HEADER_NAME;
|
||||||
|
import static run.halo.app.model.support.HaloConst.ADMIN_TOKEN_QUERY_NAME;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Admin authentication filter.
|
* Admin authentication filter.
|
||||||
*
|
*
|
||||||
|
@ -32,34 +33,8 @@ import java.util.Optional;
|
||||||
@Slf4j
|
@Slf4j
|
||||||
public class AdminAuthenticationFilter extends AbstractAuthenticationFilter {
|
public class AdminAuthenticationFilter extends AbstractAuthenticationFilter {
|
||||||
|
|
||||||
/**
|
|
||||||
* Admin session key.
|
|
||||||
*/
|
|
||||||
public final static String ADMIN_SESSION_KEY = "halo.admin.session";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Access token cache prefix.
|
|
||||||
*/
|
|
||||||
public final static String TOKEN_ACCESS_CACHE_PREFIX = "halo.admin.access.token.";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Refresh token cache prefix.
|
|
||||||
*/
|
|
||||||
public final static String TOKEN_REFRESH_CACHE_PREFIX = "halo.admin.refresh.token.";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Admin token header name.
|
|
||||||
*/
|
|
||||||
public final static String ADMIN_TOKEN_HEADER_NAME = "ADMIN-" + HttpHeaders.AUTHORIZATION;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Admin token param name.
|
|
||||||
*/
|
|
||||||
public final static String ADMIN_TOKEN_QUERY_NAME = "admin_token";
|
|
||||||
|
|
||||||
private final HaloProperties haloProperties;
|
private final HaloProperties haloProperties;
|
||||||
|
|
||||||
private final StringCacheStore cacheStore;
|
|
||||||
|
|
||||||
private final UserService userService;
|
private final UserService userService;
|
||||||
|
|
||||||
|
@ -67,8 +42,7 @@ public class AdminAuthenticationFilter extends AbstractAuthenticationFilter {
|
||||||
UserService userService,
|
UserService userService,
|
||||||
HaloProperties haloProperties,
|
HaloProperties haloProperties,
|
||||||
OptionService optionService) {
|
OptionService optionService) {
|
||||||
super(haloProperties, optionService);
|
super(haloProperties, optionService, cacheStore);
|
||||||
this.cacheStore = cacheStore;
|
|
||||||
this.userService = userService;
|
this.userService = userService;
|
||||||
this.haloProperties = haloProperties;
|
this.haloProperties = haloProperties;
|
||||||
}
|
}
|
||||||
|
@ -117,21 +91,7 @@ public class AdminAuthenticationFilter extends AbstractAuthenticationFilter {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected String getTokenFromRequest(@NonNull HttpServletRequest request) {
|
protected String getTokenFromRequest(@NonNull HttpServletRequest request) {
|
||||||
Assert.notNull(request, "Http servlet request must not be null");
|
return getTokenFromRequest(request, ADMIN_TOKEN_QUERY_NAME, ADMIN_TOKEN_HEADER_NAME);
|
||||||
|
|
||||||
// Get from header
|
|
||||||
String token = request.getHeader(ADMIN_TOKEN_HEADER_NAME);
|
|
||||||
|
|
||||||
// Get from param
|
|
||||||
if (StringUtils.isBlank(token)) {
|
|
||||||
token = request.getParameter(ADMIN_TOKEN_QUERY_NAME);
|
|
||||||
|
|
||||||
log.debug("Got token from parameter: [{}: {}]", ADMIN_TOKEN_QUERY_NAME, token);
|
|
||||||
} else {
|
|
||||||
log.debug("Got token from header: [{}: {}]", ADMIN_TOKEN_HEADER_NAME, token);
|
|
||||||
}
|
|
||||||
|
|
||||||
return token;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,9 +2,8 @@ package run.halo.app.security.filter;
|
||||||
|
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
import org.springframework.http.HttpHeaders;
|
|
||||||
import org.springframework.lang.NonNull;
|
import org.springframework.lang.NonNull;
|
||||||
import org.springframework.util.Assert;
|
import run.halo.app.cache.StringCacheStore;
|
||||||
import run.halo.app.config.properties.HaloProperties;
|
import run.halo.app.config.properties.HaloProperties;
|
||||||
import run.halo.app.exception.AuthenticationException;
|
import run.halo.app.exception.AuthenticationException;
|
||||||
import run.halo.app.exception.ForbiddenException;
|
import run.halo.app.exception.ForbiddenException;
|
||||||
|
@ -19,6 +18,8 @@ import javax.servlet.http.HttpServletResponse;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
|
|
||||||
|
import static run.halo.app.model.support.HaloConst.*;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Api authentication Filter
|
* Api authentication Filter
|
||||||
*
|
*
|
||||||
|
@ -27,15 +28,12 @@ import java.util.Optional;
|
||||||
@Slf4j
|
@Slf4j
|
||||||
public class ApiAuthenticationFilter extends AbstractAuthenticationFilter {
|
public class ApiAuthenticationFilter extends AbstractAuthenticationFilter {
|
||||||
|
|
||||||
public final static String API_ACCESS_KEY_HEADER_NAME = "API-" + HttpHeaders.AUTHORIZATION;
|
|
||||||
|
|
||||||
public final static String API_ACCESS_KEY_QUERY_NAME = "api_access_key";
|
|
||||||
|
|
||||||
private final OptionService optionService;
|
private final OptionService optionService;
|
||||||
|
|
||||||
public ApiAuthenticationFilter(HaloProperties haloProperties,
|
public ApiAuthenticationFilter(HaloProperties haloProperties,
|
||||||
OptionService optionService) {
|
OptionService optionService,
|
||||||
super(haloProperties, optionService);
|
StringCacheStore cacheStore) {
|
||||||
|
super(haloProperties, optionService, cacheStore);
|
||||||
this.optionService = optionService;
|
this.optionService = optionService;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -95,25 +93,10 @@ public class ApiAuthenticationFilter extends AbstractAuthenticationFilter {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected String getTokenFromRequest(@NonNull HttpServletRequest request) {
|
protected String getTokenFromRequest(@NonNull HttpServletRequest request) {
|
||||||
Assert.notNull(request, "Http servlet request must not be null");
|
return getTokenFromRequest(request, ADMIN_TOKEN_QUERY_NAME, ADMIN_TOKEN_HEADER_NAME);
|
||||||
|
|
||||||
// Get from header
|
|
||||||
String accessKey = request.getHeader(API_ACCESS_KEY_HEADER_NAME);
|
|
||||||
|
|
||||||
// Get from param
|
|
||||||
if (StringUtils.isBlank(accessKey)) {
|
|
||||||
accessKey = request.getParameter(API_ACCESS_KEY_QUERY_NAME);
|
|
||||||
|
|
||||||
log.debug("Got access key from parameter: [{}: {}]", API_ACCESS_KEY_QUERY_NAME, accessKey);
|
|
||||||
} else {
|
|
||||||
log.debug("Got access key from header: [{}: {}]", API_ACCESS_KEY_HEADER_NAME, accessKey);
|
|
||||||
}
|
|
||||||
|
|
||||||
return accessKey;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
package run.halo.app.security.filter;
|
package run.halo.app.security.filter;
|
||||||
|
|
||||||
|
import run.halo.app.cache.StringCacheStore;
|
||||||
import run.halo.app.config.properties.HaloProperties;
|
import run.halo.app.config.properties.HaloProperties;
|
||||||
import run.halo.app.service.OptionService;
|
import run.halo.app.service.OptionService;
|
||||||
|
|
||||||
|
@ -17,8 +18,10 @@ import java.io.IOException;
|
||||||
*/
|
*/
|
||||||
public class ContentFilter extends AbstractAuthenticationFilter {
|
public class ContentFilter extends AbstractAuthenticationFilter {
|
||||||
|
|
||||||
public ContentFilter(HaloProperties haloProperties, OptionService optionService) {
|
public ContentFilter(HaloProperties haloProperties,
|
||||||
super(haloProperties, optionService);
|
OptionService optionService,
|
||||||
|
StringCacheStore cacheStore) {
|
||||||
|
super(haloProperties, optionService, cacheStore);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
@ -4,11 +4,6 @@ import org.springframework.lang.NonNull;
|
||||||
import org.springframework.util.Assert;
|
import org.springframework.util.Assert;
|
||||||
import run.halo.app.model.entity.User;
|
import run.halo.app.model.entity.User;
|
||||||
|
|
||||||
import static run.halo.app.security.filter.AdminAuthenticationFilter.TOKEN_ACCESS_CACHE_PREFIX;
|
|
||||||
import static run.halo.app.security.filter.AdminAuthenticationFilter.TOKEN_REFRESH_CACHE_PREFIX;
|
|
||||||
import static run.halo.app.service.AdminService.ACCESS_TOKEN_CACHE_PREFIX;
|
|
||||||
import static run.halo.app.service.AdminService.REFRESH_TOKEN_CACHE_PREFIX;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Security utilities.
|
* Security utilities.
|
||||||
*
|
*
|
||||||
|
@ -17,6 +12,23 @@ import static run.halo.app.service.AdminService.REFRESH_TOKEN_CACHE_PREFIX;
|
||||||
*/
|
*/
|
||||||
public class SecurityUtils {
|
public class SecurityUtils {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Access token cache prefix.
|
||||||
|
*/
|
||||||
|
public final static String TOKEN_ACCESS_CACHE_PREFIX = "halo.admin.access.token.";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Refresh token cache prefix.
|
||||||
|
*/
|
||||||
|
public final static String TOKEN_REFRESH_CACHE_PREFIX = "halo.admin.refresh.token.";
|
||||||
|
|
||||||
|
public final static String ACCESS_TOKEN_CACHE_PREFIX = "halo.admin.access_token.";
|
||||||
|
|
||||||
|
public final static String REFRESH_TOKEN_CACHE_PREFIX = "halo.admin.refresh_token.";
|
||||||
|
|
||||||
|
public final static String TEMP_TOKEN_CACHE_PREFIX = "halo.temp.token.";
|
||||||
|
|
||||||
|
|
||||||
private SecurityUtils() {
|
private SecurityUtils() {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -47,4 +59,11 @@ public class SecurityUtils {
|
||||||
|
|
||||||
return TOKEN_REFRESH_CACHE_PREFIX + refreshToken;
|
return TOKEN_REFRESH_CACHE_PREFIX + refreshToken;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@NonNull
|
||||||
|
public static String buildTempTokenKey(@NonNull String tempToken) {
|
||||||
|
Assert.hasText(tempToken, "Temporary token must not be blank");
|
||||||
|
|
||||||
|
return TEMP_TOKEN_CACHE_PREFIX + tempToken;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -23,10 +23,6 @@ public interface AdminService {
|
||||||
|
|
||||||
int REFRESH_TOKEN_EXPIRED_DAYS = 30;
|
int REFRESH_TOKEN_EXPIRED_DAYS = 30;
|
||||||
|
|
||||||
String ACCESS_TOKEN_CACHE_PREFIX = "halo.admin.access_token.";
|
|
||||||
|
|
||||||
String REFRESH_TOKEN_CACHE_PREFIX = "halo.admin.refresh_token.";
|
|
||||||
|
|
||||||
String LOGS_PATH = "logs/spring.log";
|
String LOGS_PATH = "logs/spring.log";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -6,6 +6,7 @@ import lombok.extern.slf4j.Slf4j;
|
||||||
import org.apache.commons.io.FileUtils;
|
import org.apache.commons.io.FileUtils;
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
import org.apache.commons.lang3.time.DateFormatUtils;
|
import org.apache.commons.lang3.time.DateFormatUtils;
|
||||||
|
import org.apache.http.client.utils.URIBuilder;
|
||||||
import org.json.JSONObject;
|
import org.json.JSONObject;
|
||||||
import org.springframework.core.io.Resource;
|
import org.springframework.core.io.Resource;
|
||||||
import org.springframework.core.io.UrlResource;
|
import org.springframework.core.io.UrlResource;
|
||||||
|
@ -14,6 +15,7 @@ import org.springframework.stereotype.Service;
|
||||||
import org.springframework.util.Assert;
|
import org.springframework.util.Assert;
|
||||||
import org.springframework.web.multipart.MultipartFile;
|
import org.springframework.web.multipart.MultipartFile;
|
||||||
import org.yaml.snakeyaml.Yaml;
|
import org.yaml.snakeyaml.Yaml;
|
||||||
|
import run.halo.app.cache.StringCacheStore;
|
||||||
import run.halo.app.config.properties.HaloProperties;
|
import run.halo.app.config.properties.HaloProperties;
|
||||||
import run.halo.app.exception.NotFoundException;
|
import run.halo.app.exception.NotFoundException;
|
||||||
import run.halo.app.exception.ServiceException;
|
import run.halo.app.exception.ServiceException;
|
||||||
|
@ -22,6 +24,7 @@ import run.halo.app.model.dto.post.BasePostDetailDTO;
|
||||||
import run.halo.app.model.entity.Post;
|
import run.halo.app.model.entity.Post;
|
||||||
import run.halo.app.model.entity.Tag;
|
import run.halo.app.model.entity.Tag;
|
||||||
import run.halo.app.model.support.HaloConst;
|
import run.halo.app.model.support.HaloConst;
|
||||||
|
import run.halo.app.security.util.SecurityUtils;
|
||||||
import run.halo.app.service.BackupService;
|
import run.halo.app.service.BackupService;
|
||||||
import run.halo.app.service.OptionService;
|
import run.halo.app.service.OptionService;
|
||||||
import run.halo.app.service.PostService;
|
import run.halo.app.service.PostService;
|
||||||
|
@ -31,6 +34,7 @@ import run.halo.app.utils.HaloUtils;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.net.MalformedURLException;
|
import java.net.MalformedURLException;
|
||||||
|
import java.net.URISyntaxException;
|
||||||
import java.nio.charset.StandardCharsets;
|
import java.nio.charset.StandardCharsets;
|
||||||
import java.nio.file.Files;
|
import java.nio.file.Files;
|
||||||
import java.nio.file.NoSuchFileException;
|
import java.nio.file.NoSuchFileException;
|
||||||
|
@ -42,9 +46,12 @@ import java.util.ArrayList;
|
||||||
import java.util.LinkedHashMap;
|
import java.util.LinkedHashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
import java.util.stream.Collectors;
|
import java.util.stream.Collectors;
|
||||||
import java.util.stream.Stream;
|
import java.util.stream.Stream;
|
||||||
|
|
||||||
|
import static run.halo.app.model.support.HaloConst.*;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Backup service implementation.
|
* Backup service implementation.
|
||||||
*
|
*
|
||||||
|
@ -57,18 +64,23 @@ import java.util.stream.Stream;
|
||||||
public class BackupServiceImpl implements BackupService {
|
public class BackupServiceImpl implements BackupService {
|
||||||
|
|
||||||
private static final String LINE_SEPARATOR = System.getProperty("line.separator");
|
private static final String LINE_SEPARATOR = System.getProperty("line.separator");
|
||||||
|
public static final String BACKUP_TOKEN_KEY_PREFIX = "backup-token-";
|
||||||
|
|
||||||
private final PostService postService;
|
private final PostService postService;
|
||||||
private final PostTagService postTagService;
|
private final PostTagService postTagService;
|
||||||
private final OptionService optionService;
|
private final OptionService optionService;
|
||||||
|
private final StringCacheStore cacheStore;
|
||||||
private final HaloProperties haloProperties;
|
private final HaloProperties haloProperties;
|
||||||
|
|
||||||
public BackupServiceImpl(PostService postService,
|
public BackupServiceImpl(PostService postService,
|
||||||
PostTagService postTagService,
|
PostTagService postTagService,
|
||||||
OptionService optionService,
|
OptionService optionService,
|
||||||
|
StringCacheStore stringCacheStore,
|
||||||
HaloProperties haloProperties) {
|
HaloProperties haloProperties) {
|
||||||
this.postService = postService;
|
this.postService = postService;
|
||||||
this.postTagService = postTagService;
|
this.postTagService = postTagService;
|
||||||
this.optionService = optionService;
|
this.optionService = optionService;
|
||||||
|
this.cacheStore = stringCacheStore;
|
||||||
this.haloProperties = haloProperties;
|
this.haloProperties = haloProperties;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -162,9 +174,8 @@ public class BackupServiceImpl implements BackupService {
|
||||||
try {
|
try {
|
||||||
// Create zip path for halo zip
|
// Create zip path for halo zip
|
||||||
String haloZipFileName = HaloConst.HALO_BACKUP_PREFIX +
|
String haloZipFileName = HaloConst.HALO_BACKUP_PREFIX +
|
||||||
LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd-HH-mm-ss")) +
|
LocalDateTime.now().format(DateTimeFormatter.ofPattern("yyyy-MM-dd-HH-mm-ss-")) +
|
||||||
IdUtil.simpleUUID() +
|
IdUtil.simpleUUID().hashCode() + ".zip";
|
||||||
".zip";
|
|
||||||
// Create halo zip file
|
// Create halo zip file
|
||||||
Path haloZipPath = Files.createFile(Paths.get(haloProperties.getBackupDir(), haloZipFileName));
|
Path haloZipPath = Files.createFile(Paths.get(haloProperties.getBackupDir(), haloZipFileName));
|
||||||
|
|
||||||
|
@ -247,14 +258,16 @@ public class BackupServiceImpl implements BackupService {
|
||||||
|
|
||||||
String backupFileName = backupPath.getFileName().toString();
|
String backupFileName = backupPath.getFileName().toString();
|
||||||
BackupDTO backup = new BackupDTO();
|
BackupDTO backup = new BackupDTO();
|
||||||
backup.setDownloadUrl(buildDownloadUrl(backupFileName));
|
|
||||||
backup.setDownloadLink(backup.getDownloadUrl());
|
|
||||||
backup.setFilename(backupFileName);
|
|
||||||
try {
|
try {
|
||||||
|
backup.setDownloadUrl(buildDownloadUrl(backupFileName));
|
||||||
|
backup.setDownloadLink(backup.getDownloadUrl());
|
||||||
|
backup.setFilename(backupFileName);
|
||||||
backup.setUpdateTime(Files.getLastModifiedTime(backupPath).toMillis());
|
backup.setUpdateTime(Files.getLastModifiedTime(backupPath).toMillis());
|
||||||
backup.setFileSize(Files.size(backupPath));
|
backup.setFileSize(Files.size(backupPath));
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
throw new ServiceException("Failed to access file " + backupPath.toString(), e);
|
throw new ServiceException("Failed to access file " + backupPath, e);
|
||||||
|
} catch (URISyntaxException e) {
|
||||||
|
throw new ServiceException("Failed to generate download link for file: " + backupPath, e);
|
||||||
}
|
}
|
||||||
|
|
||||||
return backup;
|
return backup;
|
||||||
|
@ -266,9 +279,36 @@ public class BackupServiceImpl implements BackupService {
|
||||||
* @param filename filename must not be blank
|
* @param filename filename must not be blank
|
||||||
* @return download url
|
* @return download url
|
||||||
*/
|
*/
|
||||||
private String buildDownloadUrl(@NonNull String filename) {
|
private String buildDownloadUrl(@NonNull String filename) throws URISyntaxException {
|
||||||
Assert.hasText(filename, "File name must not be blank");
|
Assert.hasText(filename, "File name must not be blank");
|
||||||
|
|
||||||
return HaloUtils.compositeHttpUrl(optionService.getBlogBaseUrl(), "api/admin/backups/halo", filename);
|
// Composite http url
|
||||||
|
String backupFullUrl = HaloUtils.compositeHttpUrl(optionService.getBlogBaseUrl(), "api/admin/backups/halo", filename);
|
||||||
|
|
||||||
|
// Get temp token
|
||||||
|
String tempToken = cacheStore.get(buildBackupTokenKey(filename)).orElseGet(() -> {
|
||||||
|
String token = buildTempToken(1);
|
||||||
|
// Cache this projection
|
||||||
|
cacheStore.putIfAbsent(buildBackupTokenKey(filename), token, TEMP_TOKEN_EXPIRATION.toDays(), TimeUnit.DAYS);
|
||||||
|
return token;
|
||||||
|
});
|
||||||
|
|
||||||
|
return new URIBuilder(backupFullUrl).addParameter(TEMP_TOKEN, tempToken).toString();
|
||||||
|
}
|
||||||
|
|
||||||
|
private String buildBackupTokenKey(String backupFileName) {
|
||||||
|
return BACKUP_TOKEN_KEY_PREFIX + backupFileName;
|
||||||
|
}
|
||||||
|
|
||||||
|
private String buildTempToken(@NonNull Object value) {
|
||||||
|
Assert.notNull(value, "Temp token value must not be null");
|
||||||
|
|
||||||
|
// Generate temp token
|
||||||
|
String tempToken = HaloUtils.randomUUIDWithoutDash();
|
||||||
|
|
||||||
|
// Cache the token
|
||||||
|
cacheStore.putIfAbsent(SecurityUtils.buildTempTokenKey(tempToken), value.toString(), TEMP_TOKEN_EXPIRATION.toDays(), TimeUnit.DAYS);
|
||||||
|
|
||||||
|
return tempToken;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -5,9 +5,13 @@ import org.apache.commons.lang3.StringUtils;
|
||||||
import org.springframework.lang.NonNull;
|
import org.springframework.lang.NonNull;
|
||||||
import org.springframework.lang.Nullable;
|
import org.springframework.lang.Nullable;
|
||||||
import org.springframework.util.Assert;
|
import org.springframework.util.Assert;
|
||||||
|
import org.springframework.web.context.request.RequestContextHolder;
|
||||||
|
import org.springframework.web.context.request.ServletRequestAttributes;
|
||||||
|
|
||||||
|
import javax.servlet.http.HttpServletRequest;
|
||||||
import java.net.InetAddress;
|
import java.net.InetAddress;
|
||||||
import java.net.UnknownHostException;
|
import java.net.UnknownHostException;
|
||||||
|
import java.util.Objects;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
|
|
||||||
import static run.halo.app.model.support.HaloConst.FILE_SEPARATOR;
|
import static run.halo.app.model.support.HaloConst.FILE_SEPARATOR;
|
||||||
|
@ -26,6 +30,17 @@ public class HaloUtils {
|
||||||
|
|
||||||
public static final String URL_SEPARATOR = "/";
|
public static final String URL_SEPARATOR = "/";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Gets current http servlet request.
|
||||||
|
*
|
||||||
|
* @return current http servlet request
|
||||||
|
*/
|
||||||
|
@NonNull
|
||||||
|
public static HttpServletRequest getHttpServletRequest() {
|
||||||
|
return ((ServletRequestAttributes) Objects.requireNonNull(RequestContextHolder.getRequestAttributes()))
|
||||||
|
.getRequest();
|
||||||
|
}
|
||||||
|
|
||||||
@NonNull
|
@NonNull
|
||||||
public static String ensureBoth(@NonNull String string, @NonNull String bothfix) {
|
public static String ensureBoth(@NonNull String string, @NonNull String bothfix) {
|
||||||
return ensureBoth(string, bothfix, bothfix);
|
return ensureBoth(string, bothfix, bothfix);
|
||||||
|
|
Loading…
Reference in New Issue