Skip to content

Commit

Permalink
Merge remote-tracking branch 'upstream/main'
Browse files Browse the repository at this point in the history
  • Loading branch information
ruibaby committed Jul 2, 2024
2 parents 40e453d + c5bf192 commit d19f4a6
Show file tree
Hide file tree
Showing 24 changed files with 416 additions and 33 deletions.
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
package run.halo.app.theme.dialect;

import org.pf4j.ExtensionPoint;
import org.thymeleaf.context.ITemplateContext;
import org.thymeleaf.model.IModel;
import org.thymeleaf.model.IProcessableElementTag;
import org.thymeleaf.processor.element.IElementTagStructureHandler;
import reactor.core.publisher.Mono;

/**
* Theme template <code>footer</code> tag snippet injection processor.
*
* @author guqing
* @since 2.17.0
*/
public interface TemplateFooterProcessor extends ExtensionPoint {

Mono<Void> process(ITemplateContext context, IProcessableElementTag tag,
IElementTagStructureHandler structureHandler, IModel model);
}
Original file line number Diff line number Diff line change
Expand Up @@ -127,8 +127,12 @@ public ReactiveIndexedSessionRepository<MapSession> reactiveSessionRepository(

@Bean
DefaultUserDetailService userDetailsService(UserService userService,
RoleService roleService) {
return new DefaultUserDetailService(userService, roleService);
RoleService roleService,
HaloProperties haloProperties) {
var userDetailService = new DefaultUserDetailService(userService, roleService);
var twoFactorAuthDisabled = haloProperties.getSecurity().getTwoFactorAuth().isDisabled();
userDetailService.setTwoFactorAuthDisabled(twoFactorAuthDisabled);
return userDetailService;
}

@Bean
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -549,15 +549,22 @@ Mono<Resource> computeIfAbsent(String version, Publisher<DataBuffer> content) {
// double check of the resource
.filter(res -> isResourceMatch(res, newFilename))
.switchIfEmpty(Mono.using(
() -> tempDir.resolve(newFilename),
() -> {
if (!Files.exists(tempDir)) {
Files.createDirectories(tempDir);
}
return tempDir.resolve(newFilename);
},
path -> DataBufferUtils.write(content, path,
CREATE, TRUNCATE_EXISTING)
.then(Mono.<Resource>fromSupplier(
() -> new FileSystemResource(path)
)),
path -> {
// clean up old resource
cleanUp(this.resource);
if (shouldCleanUp(path)) {
// clean up old resource
cleanUp(this.resource);
}
})
.subscribeOn(scheduler)
.doOnNext(newResource -> this.resource = newResource)
Expand All @@ -579,6 +586,18 @@ Mono<Resource> computeIfAbsent(String version, Publisher<DataBuffer> content) {
});
}

private boolean shouldCleanUp(Path newPath) {
if (this.resource == null || !this.resource.exists()) {
return false;
}
try {
var oldPath = this.resource.getFile().toPath();
return !oldPath.equals(newPath);
} catch (IOException e) {
return false;
}
}

private static void cleanUp(Resource resource) {
if (resource instanceof WritableResource wr
&& wr.isWritable()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,18 @@ public class SecurityProperties {

private final RememberMeOptions rememberMe = new RememberMeOptions();

private final TwoFactorAuthOptions twoFactorAuth = new TwoFactorAuthOptions();

@Data
public static class TwoFactorAuthOptions {

/**
* Whether two-factor authentication is disabled.
*/
private boolean disabled;

}

@Data
public static class FrameOptions {

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -348,6 +348,7 @@ private static class DocumentConverter implements Converter<Document, HaloDocume
public HaloDocument convert(Document doc) {
var haloDoc = new HaloDocument();
haloDoc.setId(doc.get("id"));
haloDoc.setType(doc.get("type"));
haloDoc.setMetadataName(doc.get("name"));
haloDoc.setTitle(doc.get("title"));
haloDoc.setDescription(doc.get("description"));
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import static run.halo.app.security.authorization.AuthorityUtils.AUTHENTICATED_ROLE_NAME;
import static run.halo.app.security.authorization.AuthorityUtils.ROLE_PREFIX;

import lombok.Setter;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.userdetails.ReactiveUserDetailsPasswordService;
Expand All @@ -31,6 +32,12 @@ public class DefaultUserDetailService

private final RoleService roleService;

/**
* Indicates whether two-factor authentication is disabled.
*/
@Setter
private boolean twoFactorAuthDisabled;

public DefaultUserDetailService(UserService userService, RoleService roleService) {
this.userService = userService;
this.roleService = roleService;
Expand Down Expand Up @@ -66,7 +73,9 @@ public Mono<UserDetails> findByUsername(String username) {
return setAuthorities.then(Mono.fromSupplier(() -> {
var twoFactorAuthSettings = TwoFactorUtils.getTwoFactorAuthSettings(user);
return new HaloUser.Builder(userBuilder.build())
.twoFactorAuthEnabled(twoFactorAuthSettings.isAvailable())
.twoFactorAuthEnabled(
(!twoFactorAuthDisabled) && twoFactorAuthSettings.isAvailable()
)
.totpEncryptedSecret(user.getSpec().getTotpEncryptedSecret())
.build();
}));
Expand Down
Original file line number Diff line number Diff line change
@@ -1,14 +1,19 @@
package run.halo.app.theme.dialect;

import static org.thymeleaf.spring6.context.SpringContextUtils.getApplicationContext;

import org.springframework.context.ApplicationContext;
import org.thymeleaf.context.ITemplateContext;
import org.thymeleaf.model.IModel;
import org.thymeleaf.model.IProcessableElementTag;
import org.thymeleaf.processor.element.AbstractElementTagProcessor;
import org.thymeleaf.processor.element.IElementTagStructureHandler;
import org.thymeleaf.spring6.context.SpringContextUtils;
import org.thymeleaf.templatemode.TemplateMode;
import reactor.core.publisher.Flux;
import run.halo.app.infra.SystemConfigurableEnvironmentFetcher;
import run.halo.app.infra.SystemSetting;
import run.halo.app.plugin.extensionpoint.ExtensionGetter;

/**
* <p>Footer element tag processor.</p>
Expand Down Expand Up @@ -42,12 +47,23 @@ public TemplateFooterElementTagProcessor(final String dialectPrefix) {
@Override
protected void doProcess(ITemplateContext context, IProcessableElementTag tag,
IElementTagStructureHandler structureHandler) {

IModel modelToInsert = context.getModelFactory().createModel();
/*
* Obtain the Spring application context.
*/
final ApplicationContext appCtx = SpringContextUtils.getApplicationContext(context);

String globalFooterText = getGlobalFooterText(appCtx);
structureHandler.replaceWith(globalFooterText, false);
modelToInsert.add(context.getModelFactory().createText(globalFooterText));

getTemplateFooterProcessors(context)
.concatMap(processor -> processor.process(context, tag,
structureHandler, modelToInsert)
)
.then()
.block();
structureHandler.replaceWith(modelToInsert, false);
}

private String getGlobalFooterText(ApplicationContext appCtx) {
Expand All @@ -57,4 +73,13 @@ private String getGlobalFooterText(ApplicationContext appCtx) {
.map(SystemSetting.CodeInjection::getFooter)
.block();
}

private Flux<TemplateFooterProcessor> getTemplateFooterProcessors(ITemplateContext context) {
var extensionGetter = getApplicationContext(context).getBeanProvider(ExtensionGetter.class)
.getIfUnique();
if (extensionGetter == null) {
return Flux.empty();
}
return extensionGetter.getExtensions(TemplateFooterProcessor.class);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -9,4 +9,6 @@
public interface PluginFinder {

boolean available(String pluginName);

boolean available(String pluginName, String requiresVersion);
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,9 +2,10 @@

import lombok.AllArgsConstructor;
import org.apache.commons.lang3.StringUtils;
import org.pf4j.PluginManager;
import org.pf4j.PluginState;
import org.pf4j.PluginWrapper;
import run.halo.app.plugin.HaloPluginManager;
import org.springframework.util.Assert;
import run.halo.app.theme.finders.Finder;
import run.halo.app.theme.finders.PluginFinder;

Expand All @@ -17,17 +18,29 @@
@Finder("pluginFinder")
@AllArgsConstructor
public class PluginFinderImpl implements PluginFinder {
private final HaloPluginManager haloPluginManager;
private final PluginManager pluginManager;

@Override
public boolean available(String pluginName) {
if (StringUtils.isBlank(pluginName)) {
return false;
}
PluginWrapper pluginWrapper = haloPluginManager.getPlugin(pluginName);
PluginWrapper pluginWrapper = pluginManager.getPlugin(pluginName);
if (pluginWrapper == null) {
return false;
}
return PluginState.STARTED.equals(pluginWrapper.getPluginState());
}

@Override
public boolean available(String pluginName, String requiresVersion) {
Assert.notNull(requiresVersion, "Requires version must not be null.");
if (!this.available(pluginName)) {
return false;
}
var pluginWrapper = pluginManager.getPlugin(pluginName);
var pluginVersion = pluginWrapper.getDescriptor().getVersion();
return pluginManager.getVersionManager()
.checkVersionConstraint(pluginVersion, requiresVersion);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -76,3 +76,15 @@ spec:
displayName: "搜索引擎"
type: SINGLETON
description: "扩展内容搜索引擎"

---
apiVersion: plugin.halo.run/v1alpha1
kind: ExtensionPointDefinition
metadata:
name: template-footer-processor
spec:
className: run.halo.app.theme.dialect.TemplateFooterProcessor
displayName: 页脚标签内容处理器
type: MULTI_INSTANCE
description: "提供用于扩展 <halo:footer/> 标签内容的扩展方式。"

Original file line number Diff line number Diff line change
Expand Up @@ -46,6 +46,7 @@
import org.pf4j.PluginDescriptor;
import org.pf4j.PluginWrapper;
import org.springframework.core.io.buffer.DataBuffer;
import org.springframework.util.FileSystemUtils;
import org.springframework.web.server.ServerWebInputException;
import reactor.core.publisher.Mono;
import reactor.test.StepVerifier;
Expand Down Expand Up @@ -391,6 +392,24 @@ void shouldComputeBundleFileIfAbsent() {
}
})
.verifyComplete();

try {
FileSystemUtils.deleteRecursively(tempDir);
} catch (IOException e) {
throw new RuntimeException(e);
}
cache.computeIfAbsent("fake-version", fakeContent)
.as(StepVerifier::create)
.assertNext(resource -> {
try {
assertThat(Files.exists(tempDir)).isTrue();
assertEquals(tempDir.resolve("different-version.js"),
resource.getFile().toPath());
} catch (IOException e) {
throw new RuntimeException(e);
}
})
.verifyComplete();
}

@Test
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -102,6 +102,7 @@ void assertHasResult(int maxAttempts) {
assertEquals(1, hits.size());
var doc = hits.get(0);
assertEquals("post.content.halo.run-first-post", doc.getId());
assertEquals("post.content.halo.run", doc.getType());
assertEquals("first <my-tag>halo</my-tag> post", doc.getTitle());
assertNull(doc.getDescription());
assertEquals("<my-tag>halo</my-tag>", doc.getContent());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -156,11 +156,27 @@ void shouldFindHaloUserDetailsWith2faEnabledWhen2faEnabledAndTotpConfigured() {
.verifyComplete();
}

@Test
void shouldFindHaloUserDetailsWith2faDisabledWhen2faDisabledGlobally() {
userDetailService.setTwoFactorAuthDisabled(true);
var fakeUser = createFakeUser();
fakeUser.getSpec().setTwoFactorAuthEnabled(true);
fakeUser.getSpec().setTotpEncryptedSecret("fake-totp-encrypted-secret");
when(userService.getUser("faker")).thenReturn(Mono.just(fakeUser));
when(roleService.listRoleRefs(any())).thenReturn(Flux.empty());
userDetailService.findByUsername("faker")
.as(StepVerifier::create)
.assertNext(userDetails -> {
assertInstanceOf(HaloUserDetails.class, userDetails);
assertFalse(((HaloUserDetails) userDetails).isTwoFactorAuthEnabled());
})
.verifyComplete();
}

@Test
void shouldFindUserDetailsByExistingUsernameButKindOfRoleRefIsNotRole() {
var foundUser = createFakeUser();

var roleGvk = new Role().groupVersionKind();
var roleRef = new RoleRef();
roleRef.setKind("FakeRole");
roleRef.setApiGroup("fake.halo.run");
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -125,6 +125,9 @@ void globalHeadAndFooterProcessors() {
when(fetcher.fetch(eq(SystemSetting.Basic.GROUP),
eq(SystemSetting.Basic.class))).thenReturn(Mono.just(basic));

when(extensionGetter.getExtensions(TemplateFooterProcessor.class))
.thenReturn(Flux.empty());

Context context = getContext();

String result = templateEngine.process("index", context);
Expand Down Expand Up @@ -172,6 +175,9 @@ void contentHeadAndFooterAndPostProcessors() {
when(fetcher.fetch(eq(SystemSetting.Basic.GROUP),
eq(SystemSetting.Basic.class))).thenReturn(Mono.just(basic));

when(extensionGetter.getExtensions(TemplateFooterProcessor.class))
.thenReturn(Flux.empty());

String result = templateEngine.process("post", context);
assertThat(result).isEqualTo("""
<!DOCTYPE html>
Expand Down
Loading

0 comments on commit d19f4a6

Please sign in to comment.