refactor: update permission control docs for plugin (#443)

### What this PR does?
重构权限控制部分文档并调整相应结构

```release-note
None
```
This commit is contained in:
guqing
2024-10-25 16:01:56 +08:00
committed by GitHub
parent e3125c4547
commit 717ee402f4
189 changed files with 1014 additions and 454 deletions

View File

@@ -52,7 +52,7 @@ public interface ExtensionGetter {
通过使用 `ExtensionGetter`,开发者可以轻松地在插件中访问和管理各种扩展点,提升插件的功能和灵活性。
如果想了解 Halo 提供的扩展点请参考:[扩展点](./extension-points/index.md)。
如果想了解 Halo 提供的扩展点请参考:[扩展点](../../extension-points/server/index.md)。
:::
### 示例

View File

@@ -1,76 +0,0 @@
---
title: Web 过滤器
description: 为 Web 请求提供过滤器扩展点,可用于对请求进行拦截、修改等操作。
---
在现代的 Web 应用开发中过滤器Filter是一个非常重要的概念。你可以使用 `run.halo.app.security.AdditionalWebFilter` 在服务器处理请求之前或之后执行特定的任务。
通过实现这个接口,开发者可以自定义过滤逻辑,用于处理进入和离开应用程序的 HTTP 请求和响应。
AdditionalWebFilter 能做什么?
1. 认证与授权: AdditionalWebFilter 可以用来检查用户是否登录,或者是否有权限访问某个资源。
2. 日志记录与审计: 在请求处理之前或之后记录日志,帮助了解应用程序的使用情况。
3. 请求重构: 修改请求数据,例如添加、删除或修改请求头或请求参数。
4. 响应处理: 修改响应,例如设置通用的响应头。
5. 性能监控: 记录处理请求所需的时间,用于性能分析。
6. 异常处理: 统一处理请求过程中抛出的异常。
7. ......
## 使用示例
以下是一个使用 `AdditionalWebFilter` 来拦截 `/login` 请求实现用户名密码登录的示例:
```java
@Component
public class UsernamePasswordAuthenticator implements AdditionalWebFilter {
final ServerWebExchangeMatcher requiresMatcher = ServerWebExchangeMatchers.pathMatchers(HttpMethod.POST, "/login");
@Override
@NonNull
public Mono<Void> filter(@NonNull ServerWebExchange exchange, @NonNull WebFilterChain chain) {
return this.requiresAuthenticationMatcher.matches(exchange)
.filter((matchResult) -> {
return matchResult.isMatch();
}).flatMap((matchResult) -> {
return this.authenticationConverter.convert(exchange);
}).switchIfEmpty(chain.filter(exchange)
.then(Mono.empty()))
.flatMap((token) -> {
return this.authenticate(exchange, chain, token);
}).onErrorResume(AuthenticationException.class, (ex) -> {
return this.authenticationFailureHandler.onAuthenticationFailure(new WebFilterExchange(exchange, chain), ex);
});
}
@Override
public int getOrder() {
return SecurityWebFiltersOrder.FORM_LOGIN.getOrder();
}
}
```
1. `filter` 方法中的 `chain.filter(exchange)` 表示继续执行后续的过滤器,如果不调用这个方法,请求将不会继续执行后续的过滤器或目标处理程序。
2. `getOrder` 方法用于指定过滤器的执行顺序,比如 `SecurityWebFiltersOrder.FORM_LOGIN.getOrder()` 表示在 Spring Security 的表单登录过滤器之前执行,参考:[SecurityWebFiltersOrder](https://github.com/spring-projects/spring-security/blob/main/config/src/main/java/org/springframework/security/config/web/server/SecurityWebFiltersOrder.java)。
`AdditionalWebFilter` 对应的 `ExtensionPointDefinition` 如下:
```yaml
apiVersion: plugin.halo.run/v1alpha1
kind: ExtensionPointDefinition
metadata:
name: additional-webfilter
spec:
className: run.halo.app.security.AdditionalWebFilter
displayName: AdditionalWebFilter
type: MULTI_INSTANCE
description: "Contract for interception-style, chained processing of Web requests that may be used to
implement cross-cutting, application-agnostic requirements such as security, timeouts, and others."
```
即声明 `ExtensionDefinition` 自定义模型对象时对应的 `extensionPointName``additional-webfilter`
以下是一些可以参考的项目示例:
- [OAuth2 第三方登录插件](https://github.com/halo-sigs/plugin-oauth2)
- [Halo 用户名密码登录](https://github.com/halo-dev/halo/blob/main/application/src/main/java/run/halo/app/security/authentication/login/UsernamePasswordAuthenticator.java)

View File

@@ -1,55 +0,0 @@
---
title: 附件存储
description: 为附件存储方式提供的扩展点,可用于自定义附件存储方式。
---
附件存储策略扩展点支持扩展附件的上传和存储方式,如将附件存储到第三方云存储服务中。
扩展点接口如下:
```java
public interface AttachmentHandler extends ExtensionPoint {
Mono<Attachment> upload(UploadContext context);
Mono<Attachment> delete(DeleteContext context);
default Mono<URI> getSharedURL(Attachment attachment,
Policy policy,
ConfigMap configMap,
Duration ttl) {
return Mono.empty();
}
default Mono<URI> getPermalink(Attachment attachment,
Policy policy,
ConfigMap configMap) {
return Mono.empty();
}
```
- `upload` 方法用于上传附件,返回值为 `Mono<Attachment>`,其中 `Attachment` 为上传成功后的附件对象。
- `delete` 方法用于删除附件,返回值为 `Mono<Attachment>`,其中 `Attachment` 为删除后的附件对象。
- `getSharedURL` 方法用于获取附件的共享链接,返回值为 `Mono<URI>`,其中 `URI` 为附件的共享链接。
- `getPermalink` 方法用于获取附件的永久链接,返回值为 `Mono<URI>`,其中 `URI` 为附件的永久链接。
`AttachmentHandler` 对应的 `ExtensionPointDefinition` 如下:
```yaml
apiVersion: plugin.halo.run/v1alpha1
kind: ExtensionPointDefinition
metadata:
name: attachment-handler
spec:
className: run.halo.app.core.extension.attachment.endpoint.AttachmentHandler
displayName: AttachmentHandler
type: MULTI_INSTANCE
description: "Provide extension points for attachment storage strategies"
```
即声明 `ExtensionDefinition` 自定义模型对象时对应的 `extensionPointName``attachment-handler`
可以参考以下项目示例:
- [S3 对象存储协议的存储插件](https://github.com/halo-dev/plugin-s3)
- [阿里云 OSS 的存储策略插件](https://github.com/halo-sigs/plugin-alioss)
- [又拍云 OSS 的存储策略](https://github.com/AirboZH/plugin-uposs)

View File

@@ -1,81 +0,0 @@
---
title: 认证安全过滤器
description: 提供 Security WebFilter 扩展点插件可实现自定义认证逻辑例如用户名密码认证JWT 认证,匿名认证。
---
此前Halo 提供了 AdditionalWebFilter 作为扩展点供插件扩展认证相关的功能。但是近期我们明确了 AdditionalWebFilter
的使用用途,故不再作为认证的扩展点。
目前Halo 提供了三种认证扩展点:表单登录认证、普通认证和匿名认证。
## 表单登录FormLogin
示例如下:
```java
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.WebFilterChain;
import reactor.core.publisher.Mono;
import run.halo.app.security.FormLoginSecurityWebFilter;
@Component
public class MyFormLoginSecurityWebFilter implements FormLoginSecurityWebFilter {
@Override
public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
// Do your logic here
return chain.filter(exchange);
}
}
```
## 普通认证Authentication
示例如下:
```java
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.WebFilterChain;
import reactor.core.publisher.Mono;
import run.halo.app.security.AuthenticationSecurityWebFilter;
@Component
public class MyAuthenticationSecurityWebFilter implements AuthenticationSecurityWebFilter {
@Override
public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
// Do your logic here
return chain.filter(exchange);
}
}
```
## 匿名认证Anonymous Authentication
示例如下:
```java
import org.springframework.stereotype.Component;
import org.springframework.web.server.ServerWebExchange;
import org.springframework.web.server.WebFilterChain;
import reactor.core.publisher.Mono;
import run.halo.app.security.AnonymousAuthenticationSecurityWebFilter;
@Component
public class MyAnonymousAuthenticationSecurityWebFilter
implements AnonymousAuthenticationSecurityWebFilter {
@Override
public Mono<Void> filter(ServerWebExchange exchange, WebFilterChain chain) {
// Do your logic here
return chain.filter(exchange);
}
}
```
我们在实现扩展点的时候需要注意:如果当前请求不满足认证条件,请一定要调用 `chain.filter(exchange)`,给其他 filter 留下机会。
后续会根据需求实现其他认证相关的扩展点。

View File

@@ -1,83 +0,0 @@
---
title: 评论主体展示
description: 用于在管理端评论列表中展示评论的主体内容。
---
评论主体扩展点用于在管理端评论列表中展示评论的主体内容,评论自定义模型是 Halo 主应用提供的,如果你需要使用评论自定义模型,那么评论会统一
展示在管理后台的评论列表中,这时就需要通过评论主体扩展点来展示评论的主体内容便于跳转到对应的页面,如果没有实现该扩展点,那么评论列表中对应的评论的主体会显示为“未知”。
```java
public interface CommentSubject<T extends Extension> extends ExtensionPoint {
Mono<T> get(String name);
default Mono<SubjectDisplay> getSubjectDisplay(String name) {
return Mono.empty();
}
boolean supports(Ref ref);
record SubjectDisplay(String title, String url, String kindName) {
}
}
```
- `get` 方法用于获取评论主体,参数 `name` 是评论主体的自定义模型对象的名称,返回值为 `Mono<T>`,其中 `T` 为评论主体对象,它是使用评论的那个自定义模型。
- `getSubjectDisplay` 方法用于获取评论主体的展示信息,返回值为 `Mono<SubjectDisplay>`,其中 `SubjectDisplay` 为评论主体的展示信息,包含标题、链接和类型名称,用于在主题端展示评论主体的信息。
- `supports` 方法用于判断是否支持该评论主体,返回值为 `boolean`,如果支持则返回 `true`,否则返回 `false`
实现该扩展点后,评论列表会通过 `get` 方法将主体的自定义模型对象带到评论列表中,可以配置前端的扩展点来决定如何展示评论主体的信息,参考:[UI 评论来源显示](../../ui/extension-points//comment-subject-ref-create.md)
例如对于文章是支持评论的,所以文章的评论主体扩展点实现如下:
```java
public class PostCommentSubject implements CommentSubject<Post> {
private final ReactiveExtensionClient client;
private final ExternalLinkProcessor externalLinkProcessor;
@Override
public Mono<Post> get(String name) {
return client.fetch(Post.class, name);
}
@Override
public Mono<SubjectDisplay> getSubjectDisplay(String name) {
return get(name)
.map(post -> {
var url = externalLinkProcessor
.processLink(post.getStatusOrDefault().getPermalink());
return new SubjectDisplay(post.getSpec().getTitle(), url, "文章");
});
}
@Override
public boolean supports(Ref ref) {
Assert.notNull(ref, "Subject ref must not be null.");
GroupVersionKind groupVersionKind =
new GroupVersionKind(ref.getGroup(), ref.getVersion(), ref.getKind());
return GroupVersionKind.fromExtension(Post.class).equals(groupVersionKind);
}
}
```
`CommentSubject` 对应的 `ExtensionPointDefinition` 如下:
```yaml
apiVersion: plugin.halo.run/v1alpha1
kind: ExtensionPointDefinition
metadata:
name: comment-subject
spec:
className: run.halo.app.content.comment.CommentSubject
displayName: CommentSubject
type: MULTI_INSTANCE
description: "Provide extension points for comment subject display"
```
即声明 `ExtensionDefinition` 自定义模型对象时对应的 `extensionPointName``comment-subject`
可以参考其他使用该扩展点的项目示例:
- [Halo 自定义页面评论主体](https://github.com/halo-dev/halo/blob/main/application/src/main/java/run/halo/app/content/comment/SinglePageCommentSubject.java)
- [瞬间的评论主体](https://github.com/halo-sigs/plugin-moments/blob/096b1b3e4a2ca44b6f858ba1181b62eeff64a139/src/main/java/run/halo/moments/MomentCommentSubject.java#L25)

View File

@@ -1,41 +0,0 @@
---
title: 评论组件
description: 用于自定义评论组件,可在主题端使用其他评论组件。
---
评论组件扩展点用于自定义主题端使用的评论组件Halo 通过插件提供了一个默认的评论组件,如果你需要使用其他的评论组件,那么可以通过实现该扩展点来自定义评论组件。
```java
public interface CommentWidget extends ExtensionPoint {
String ENABLE_COMMENT_ATTRIBUTE = CommentWidget.class.getName() + ".ENABLE";
void render(ITemplateContext context, IProcessableElementTag tag,
IElementTagStructureHandler structureHandler);
}
```
其中 `render` 方法用于在主题端模板中渲染评论组件,参数:
- `context` 为模板上下文,包含执行模板的上下文:变量、模板数据等。
- 参数 `tag``<halo:comment />` 标签它包含元素的名称及其属性
- `structureHandler` 是一个特殊的对象,它允许 `CommentWidget` 向引擎发出指令,指示模板引擎应根据处理器的执行而执行哪些操作。
`CommentWidget` 对应的 `ExtensionPointDefinition` 如下:
```yaml
apiVersion: plugin.halo.run/v1alpha1
kind: ExtensionPointDefinition
metadata:
name: comment-widget
spec:
className: run.halo.app.theme.dialect.CommentWidget
displayName: CommentWidget
type: SINGLETON
description: "Provides an extension point for the comment widget on the theme-side."
```
即声明 `ExtensionDefinition` 自定义模型对象时对应的 `extensionPointName``comment-widget`
参考:[Thymeleaf IElementTagProcessor 文档](https://www.thymeleaf.org/doc/tutorials/3.1/extendingthymeleaf.html#element-tag-processors-ielementtagprocessor)。
参考:[Halo 默认评论组件的实现](https://github.com/halo-dev/plugin-comment-widget/blob/main/src/main/java/run/halo/comment/widget/DefaultCommentWidget.java)。

View File

@@ -1,65 +0,0 @@
---
title: 扩展点
description: Halo 服务端为插件提供的扩展点接口
---
术语:
- 扩展点
- 由 Halo 定义的用于添加特定功能的接口。
- 扩展点应该在服务的核心功能和它所认为的集成之间的交叉点上。
- 扩展点是对服务的扩充,但不是影响服务的核心功能:区别在于,如果没有其核心功能,服务就无法运行,而扩展点对于特定的配置可能至关重要该服务最终是可选的。
- 扩展点应该小且可组合,并且在相互配合使用时,可为 Halo 提供比其各部分总和更大的价值。
- 扩展
- 扩展点的一种具体实现。
使用 Halo 扩展点的必要步骤是:
1. 实现扩展点接口,然后标记上 `@Component` 注解。
2. 对该扩展点的实现类进行 `ExtensionDefinition` 自定义模型对象的声明。
例如: 实现一个通知器的扩展,首先 `implements` ReactiveNotifier 扩展点接口:
```java
@Component
public class EmailNotifier implements ReactiveNotifier {
@Override
public Mono<Void> notify(NotificationContext context) {
// Send notification
}
}
```
然后声明一个 `ExtensionDefinition` 自定义模型对象:
```yaml
apiVersion: plugin.halo.run/v1alpha1
kind: ExtensionDefinition
metadata:
name: halo-email-notifier # 指定一个扩展定义的名称
spec:
# 扩展的全限定类名
className: run.halo.app.notification.EmailNotifier
# 所实现的扩展点定义的自定义模型对象名称
extensionPointName: reactive-notifier
# 扩展名称用于展示给用户
displayName: "EmailNotifier"
# 扩展的简要描述,用于让用户了解该扩展的作用
description: "Support sending notifications to users via email"
```
:::tip Note
单实例或多实例的扩展点需要声明对应的 `ExtensionPointDefinition` 自定义模型对象被称之为扩展点定义,用于描述该扩展点的信息,例如:扩展点的名称、描述、扩展点的类型等。
单实例或多实例扩展点的实现也必须声明一个对应的 `ExtensionDefinition` 自定义模型对象被称之为扩展定义,用于描述该扩展的信息,例如:扩展的名称、描述、对应扩展点的对象名称等。
:::
关于如何在插件中声明自定义模型对象请参考:[自定义模型](../../server/extension.md#declare-extension-object)
以下是目前已支持的扩展点列表:
```mdx-code-block
import DocCardList from '@theme/DocCardList';
<DocCardList />
```

View File

@@ -1,60 +0,0 @@
---
title: 通知器
description: 为以何种方式向用户发送通知提供的扩展点。
---
通知器扩展点是用于扩展为 Halo 通知系统提供更多通知方式的扩展点例如邮件、短信、WebHook 等。
```java
public interface ReactiveNotifier extends ExtensionPoint {
Mono<Void> notify(NotificationContext context);
}
```
`notify` 方法用于发送通知参数context 为通知上下文,包含通知的内容、接收者、通知配置等信息。
除了实现该扩展点并声明 `ExtensionDefinition` 自定义模型对象外,还需要声明一个 `NotifierDescriptor` 自定义模型对象用于描述通知器,例如:
```yaml
apiVersion: notification.halo.run/v1alpha1
kind: NotifierDescriptor
metadata:
name: default-email-notifier
spec:
displayName: '邮件通知'
description: '通过邮件将通知发送给用户'
notifierExtName: 'halo-email-notifier'
senderSettingRef:
name: 'notifier-setting-for-email'
group: 'sender'
#receiverSettingRef:
# name: ''
# group: ''
```
- `notifierExtName` 为通知器扩展的自定义模型对象名称
- `senderSettingRef` 用于声明通知器的发送者配置例如邮件通知器的发送者配置为SMTP 服务器地址、端口、用户名、密码等,如果没有可以不配置,参考:[表单定义](../../../../form-schema.md)
- `name` 为发送者配置的名称,它是一个 `Setting` 自定义模型对象的名称。
- `group` 用于引用到一个具体的配置 Schema 组,它是一个 `Setting` 自定义模型对象中描述的 `formSchema``group`,由于 `Setting` 可以声明多个配置分组但通知器的发送者配置只能有在一个组,因此需要指定一个组。
- `receiverSettingRef` 用于声明通知器的接收者配置,例如:邮件通知器的接收者配置为:接收者邮箱地址,如果没有可以不配置,`name``group` 配置同 `senderSettingRef`
当配置了 `senderSettingRef` 后,触发通知时 `notify` 方法的 `context` 参数中会包含 `senderConfig` 即为发送者配置的值,`receiverConfig` 同理。
`ReactiveNotifier` 对应的 `ExtensionPointDefinition` 如下:
```yaml
apiVersion: plugin.halo.run/v1alpha1
kind: ExtensionPointDefinition
metadata:
name: reactive-notifier
spec:
className: run.halo.app.notification.ReactiveNotifier
displayName: Notifier
type: MULTI_INSTANCE
description: "Provides a way to extend the notifier to send notifications to users."
```
即声明 `ExtensionDefinition` 自定义模型对象时对应的 `extensionPointName``reactive-notifier`
使用案例可以参考:[Halo 邮件通知器](https://github.com/halo-dev/halo/blob/main/application/src/main/java/run/halo/app/notification/EmailNotifier.java)

View File

@@ -1,43 +0,0 @@
---
title: 主题端文章内容处理
description: 提供扩展主题端文章内容处理的方法,干预文章内容的渲染。
---
主题端文章内容处理扩展点用于干预文章内容的渲染,例如:在文章内容中添加广告、添加版权信息等。
```java
public interface ReactivePostContentHandler extends ExtensionPoint {
Mono<PostContentContext> handle(@NonNull PostContentContext postContent);
@Data
@Builder
class PostContentContext {
private Post post;
private String content;
private String raw;
private String rawType;
}
}
```
`handle` 方法用于处理文章内容,参数 `postContent` 为文章内容上下文,包含文章自定义模型对象、文章 html 内容、原始内容、原始内容类型等信息。
`ReactivePostContentHandler` 对应的 `ExtensionPointDefinition` 如下:
```yaml
apiVersion: plugin.halo.run/v1alpha1
kind: ExtensionPointDefinition
metadata:
name: reactive-post-content-handler
spec:
className: run.halo.app.theme.ReactivePostContentHandler
displayName: ReactivePostContentHandler
type: MULTI_INSTANCE
description: "Provides a way to extend the post content to be displayed on the theme-side."
```
即声明 `ExtensionDefinition` 自定义模型对象时对应的 `extensionPointName``reactive-post-content-handler`
使用案例可以参考:[WebP Cloud 插件](https://github.com/webp-sh/halo-plugin-webp-cloud/blob/a6069dfa78931de0d5b5dfe98fdd18a0da75b09f/src/main/java/se/webp/plugin/WebpCloudPostContentHandler.java#L17)
它的作用是处理主题端文章内容中的所有图片的地址,将其替换为一个 WebP Cloud 的代理地址,从而实现文章内容中的图片都使用 WebP 格式。

View File

@@ -1,38 +0,0 @@
---
title: 主题端自定义页面内容处理
description: 提供扩展主题端自定义页面内容处理的方法,干预自定义页面内容的渲染。
---
主题端自定义页面内容处理扩展点,作用同 [主题端文章内容处理](./post-content.md) 扩展点,只是作用于自定义页面。
```java
public interface ReactiveSinglePageContentHandler extends ExtensionPoint {
Mono<SinglePageContentContext> handle(@NonNull SinglePageContentContext singlePageContent);
@Data
@Builder
class SinglePageContentContext {
private SinglePage singlePage;
private String content;
private String raw;
private String rawType;
}
}
```
`ReactiveSinglePageContentHandler` 对应的 `ExtensionPointDefinition` 如下:
```yaml
apiVersion: plugin.halo.run/v1alpha1
kind: ExtensionPointDefinition
metadata:
name: reactive-singlepage-content-handler
spec:
className: run.halo.app.theme.ReactiveSinglePageContentHandler
displayName: ReactiveSinglePageContentHandler
type: MULTI_INSTANCE
description: "Provides a way to extend the single page content to be displayed on the theme-side."
```
即声明 `ExtensionDefinition` 自定义模型对象时对应的 `extensionPointName``reactive-singlepage-content-handler`

View File

@@ -1,77 +0,0 @@
---
title: 主题端 Halo Footer 标签处理
description: 提供扩展主题端 HTML 页面中的 <halo:footer/> 标签内容处理的方法。
---
Halo 为主题端模板提供了自定义标签 `<halo:footer/>` 的处理扩展点,以便可以添加额外的页脚内容如版权信息、备案号等。
## 使用场景
- 添加备案号
- 添加版权信息
- 添加统计代码
- 添加自定义脚本
- 添加自定义链接
## 扩展点定义
扩展 `<halo:footer/>` 标签的扩展点定义为 `TemplateFooterProcessor`,对应的 `ExtensionPoint` 类型为 `MULTI_INSTANCE`,即可以有多个实现类。
```java
public interface TemplateFooterProcessor extends ExtensionPoint {
Mono<Void> process(ITemplateContext context, IProcessableElementTag tag,
IElementTagStructureHandler structureHandler, IModel model);
}
```
`TemplateFooterProcessor` 对应的 `ExtensionPointDefinition` 资源描述如下:
```yaml
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/> 标签内容的扩展方式。"
```
即声明 `ExtensionDefinition` 自定义模型对象时对应的 `extensionPointName``template-footer-processor`
## 示例实现
以下是一个简单的 TemplateFooterProcessor 插件实现示例:
```java
@Component
public class FakeFooterCodeInjection implements TemplateFooterProcessor {
@Override
public Mono<Void> process(ITemplateContext context, IProcessableElementTag tag,
IElementTagStructureHandler structureHandler, IModel model) {
var factory = context.getModelFactory();
// regular footer text
var copyRight = factory.createText("<div>© 2024 Halo</div>");
model.add(copyRight);
return Mono.empty();
}
}
```
声明 ExtensionDefinition 自定义模型对象时对应的 extensionPointName 为 template-footer-processor。
```yaml
apiVersion: plugin.halo.run/v1alpha1
kind: ExtensionDefinition
metadata:
name: custom-footer-extension
spec:
extensionPointName: template-footer-processor
className: com.example.FakeFooterCodeInjection
displayName: "Custom Footer Extension"
description: "Adds custom footer content."
icon: 'some-icon'
```

View File

@@ -1,87 +0,0 @@
---
title: 主题端 HTML Head 标签处理
description: 提供扩展主题端 HTML 页面中的 Head 标签内容处理的方法,干预 HTML 页面的 Head 标签内容。
---
主题端 HTML Head 标签处理扩展点的作用是干预 HTML 页面中的 Head 标签内容,可以添加自定义的 CSS、JS 及 meta 标签等,以满足特定的定制化需求。
## 使用场景
- **添加自定义样式或脚本**:在 HTML Head 中插入额外的 CSS 文件或 JavaScript 脚本文件,以增强页面的交互性或样式。
- **定制 Meta 标签**:为特定页面添加或修改 meta 标签,如描述、作者、关键词等,以提高 SEO 和页面信息的完整性。
- **引入第三方库**:引入第三方库(如 Google Fonts、Font Awesome 等),以满足页面的特殊功能或风格需求。
- **定制 Open Graph 等社交媒体标签**:为社交媒体分享优化页面标签内容。
## 扩展点定义
主题端 HTML Head 标签处理的扩展点定义为 `TemplateHeadProcessor`,对应的 `ExtensionPoint` 类型为 `MULTI_INSTANCE`,即可以有多个实现类。
```java
@FunctionalInterface
public interface TemplateHeadProcessor extends ExtensionPoint {
Mono<Void> process(ITemplateContext context, IModel model,
IElementModelStructureHandler structureHandler);
}
```
`TemplateHeadProcessor` 对应的 `ExtensionPointDefinition` 资源描述如下:
```yaml
apiVersion: plugin.halo.run/v1alpha1
kind: ExtensionPointDefinition
metadata:
name: template-head-processor
spec:
className: run.halo.app.theme.dialect.TemplateHeadProcessor
displayName: TemplateHeadProcessor
type: MULTI_INSTANCE
description: "Provides a way to extend the head tag content in the theme-side HTML page."
```
即声明 `ExtensionDefinition` 自定义模型对象时对应的 `extensionPointName``template-head-processor`
## 示例实现
以下是一个简单的 TemplateHeadProcessor 插件实现示例:
```java
@Component
public class CustomHeadProcessor implements TemplateHeadProcessor {
@Override
public Mono<Void> process(ITemplateContext context, IModel model,
IElementModelStructureHandler structureHandler) {
// 添加自定义 CSS 文件
model.add(context.createStandaloneElementTag("link",
"rel", "stylesheet",
"href", "/custom/styles.css"));
// 添加自定义 Meta 标签
model.add(context.createStandaloneElementTag("meta",
"name", "author",
"content", "Your Name"));
return Mono.empty();
}
}
```
声明 ExtensionDefinition 自定义模型对象时对应的 extensionPointName 为 template-head-processor。
```yaml
apiVersion: plugin.halo.run/v1alpha1
kind: ExtensionDefinition
metadata:
name: custom-head-extension
spec:
extensionPointName: template-head-processor
className: com.example.CustomHeadProcessor
displayName: "Custom Head Extension"
description: "Adds custom CSS and meta tags to the head section."
```
## 使用此扩展点的插件
- [集成 highlight.js 为文章提供代码块高亮渲染](https://github.com/halo-sigs/plugin-highlightjs)
- [集成 lightgallery.js支持在内容页面放大显示图片](https://github.com/halo-sigs/plugin-lightgallery)
- [Halo 2.0 对 Umami 的集成](https://github.com/halo-sigs/plugin-umami)

View File

@@ -1,30 +0,0 @@
---
title: 用户名密码认证管理器
description: 提供扩展用户名密码的身份验证的方法
---
用户名密码认证管理器扩展点用于替换 Halo 默认的用户名密码认证管理器实现,例如:使用第三方的身份验证服务,一个例子是 LDAP。
```java
public interface UsernamePasswordAuthenticationManager extends ExtensionPoint {
Mono<Authentication> authenticate(Authentication authentication);
}
```
`UsernamePasswordAuthenticationManager` 对应的 `ExtensionPointDefinition` 如下:
```yaml
apiVersion: plugin.halo.run/v1alpha1
kind: ExtensionPointDefinition
metadata:
name: username-password-authentication-manager
spec:
className: run.halo.app.security.authentication.login.UsernamePasswordAuthenticationManager
displayName: Username password authentication manager
type: SINGLETON
description: "Provides a way to extend the username password authentication."
```
即声明 `ExtensionDefinition` 自定义模型对象时对应的 `extensionPointName``username-password-authentication-manager`
可以参考的实现示例 [TOTP 认证](https://github.com/halo-dev/halo/blob/86e688a15d05c084021b6ba5e75d56a8813c3813/application/src/main/java/run/halo/app/security/authentication/twofactor/totp/TotpAuthenticationFilter.java#L84)

View File

@@ -1,307 +0,0 @@
---
title: API 权限控制
description: 了解如何对插件中的 API 定义角色模板以接入权限控制
---
插件中的 APIs 无论是自定义模型自动生成的 APIs 或者是通过 `@Controller` 自定义的 APIs 都只有超级管理员能够访问,如果想将这些 APIs 授权给其他用户访问,
则需要定义一些[角色模板](../../basics/framework.md#rbac)的资源以便可以在用户界面上将其分配给其他角色使用。
## 角色模板定义
定义角色模板需要遵循一定的规范:
- **文件位置和标记**:角色模板定义文件存放于 `src/main/resources/extensions`,文件名可以任意,它的 kind 为 Role 且必须具有标签 `halo.run/role-template: "true"` 来标识其为模板。
- **角色类型**:通常,我们为同一种资源定义两种角色模板:只读权限和管理权限,分别对应 `view``manage`,如果需要更细粒度的控制,可以定义更多的角色模板。
- **角色名称**:角色名称必须以插件名作为前缀,以避免与其他插件冲突,例如 `my-plugin-role-view-persons`
- **角色依赖**:如果一个角色需要依赖于另一个角色,可以通过 `rbac.authorization.halo.run/dependencies` 作为 key 的 `metadata.annotations` 来声明依赖关系。
- **UI 权限**:如果需要在前端界面上控制某个角色的权限,可以通过 `rbac.authorization.halo.run/ui-permissions` 作为 key 的 `metadata.annotations` 来声明。
- **角色模板分组**:如果需要将多个角色模板归为一组显示,可以通过 `rbac.authorization.halo.run/module` 作为 key 的 `metadata.annotations` 来声明分组名称。
- **角色显示名称**:如果需要在前端界面上显示角色的友好名称,可以通过 `rbac.authorization.halo.run/display-name` 作为 key 的 `metadata.annotations` 来声明显示名称。
- **隐藏角色模板**:如果不想在前端界面上显示某个角色模板,可以通过 `halo.run/hidden: "true"``metadata.labels` 来隐藏角色模板。
角色模板定义的基本框架如下:
```yaml
apiVersion: v1alpha1
kind: Role
metadata:
name: role-template-name
labels:
halo.run/role-template: "true"
rules:
- apiGroups: []
resources: []
resourceNames: []
verbs: []
- nonResourceURLs: []
verbs: []
```
在遵循上述规范的基础上,最重要的是定义 `rules` 字段,它是一个数组,用于定义角色模板的权限规则,规则分为两种类型:[资源型](#resource-rules)和[非资源型](#non-resource-rules)。
### 资源型规则 {#resource-rules}
资源型规则用于定义对资源的操作权限API 符合以下特征:
-`/api` 开头,且以 `/api/<version>/<resource>[/<resourceName>/<subresource>]` 规则组成 APIs最少路径层级为 3 即 `/api/<version>/<resource>`,最多路径层级为 5 即包含 `<resourceName>``<subresource>`,例如 `/api/v1/posts`
-`/apis/<group>/<version>/<resource>[/<resourceName>/<subresource>]` 规则组成的 APIs最少路径层级为 4 即 `/apis/<group>/<version>/<resource>`,最多路径层级为 6 即包含 `<resourceName>``<subresource>`,例如 `/apis/my-plugin.halo.run/v1alpha1/persons`
:::info 注
`[]`包裹的部分表示可选,`/api` 前缀被 Halo 保留,不允许插件定义以 `/api` 开头的资源型 APIs所以插件的资源型 APIs 都是以 `/apis` 开头的。
:::
通常可以通过 `apiGroups``resources``resourceNames``verbs` 来组合定义。
例如对于资源型 API `GET /apis/my-plugin.halo.run/v1alpha1/persons`,可以定义如下规则:
```yaml
rules:
- apiGroups: [ "my-plugin.halo.run" ]
resources: [ "my-plugin/persons" ]
verbs: [ "list" ]
```
而对于资源型 API `GET /apis/my-plugin.halo.run/v1alpha1/persons/zhangsan`,可以定义如下规则:
```yaml
rules:
- apiGroups: [ "my-plugin.halo.run" ]
resources: [ "my-plugin/persons" ]
resourceNames: [ "zhangsan" ]
verbs: [ "get" ]
```
关于 `verbs` 的详细说明请参考 [Verbs 详解](#verbs)。
### 非资源型规则 {#non-resource-rules}
凡是不符合资源型 APIs 规则的 APIs 都被定型为非资源型 APIs例如 `/healthz`,可以使用以下配置方式:
```yaml
rules:
- nonResourceURLs: ["/healthz", "/healthz/*"]
verbs: [ "get", "create"]
```
非资源型规则使用 `nonResourceURLs` 来定义,其中 `nonResourceURLs` 是一个字符串数组,用于定义非资源型 APIs 的路径,`verbs` 用于定义非资源型 APIs 的请求动词。
`nonResourceURL` 中的 `*` 是一个全局通配符,表示匹配所有路径,如 `/healthz/*` 表示匹配 `/healthz/` 下的所有路径。
### 示例:定义人员管理角色模板
以下 YAML 文件展示了如何定义用于人员管理的角色模板:
```yaml
apiVersion: v1alpha1
kind: Role
metadata:
# 使用 plugin name 作为前缀防止与其他插件冲突,比如这里的 my-plugin
name: my-plugin-role-view-persons
labels:
halo.run/role-template: "true"
annotations:
rbac.authorization.halo.run/module: "Persons Management"
rbac.authorization.halo.run/display-name: "Person Manage"
rbac.authorization.halo.run/ui-permissions: |
["plugin:my-plugin:person:view"]
rules:
- apiGroups: ["my-plugin.halo.run"]
resources: ["my-plugin/persons"]
verbs: ["*"]
---
apiVersion: v1alpha1
kind: Role
metadata:
name: my-plugin-role-manage-persons
labels:
halo.run/role-template: "true"
annotations:
rbac.authorization.halo.run/dependencies: |
[ "role-template-view-person" ]
rbac.authorization.halo.run/module: "Persons Management"
rbac.authorization.halo.run/display-name: "Person Manage"
rbac.authorization.halo.run/ui-permissions: |
["plugin:my-plugin:person:manage"]
rules:
- apiGroups: [ "my-plugin.halo.run" ]
resources: [ "my-plugin/persons" ]
verbs: [ "get", "list" ]
```
上述便是根据 [自定义模型](./extension.md) 章节中定义的 Person 自定义模型来配置角色模板的示例。
1. 定义了一个用于管理 Person 自定义模型对象的角色模板 `my-plugin-role-manage-persons`,它具有所有权限。
2. 定义了一个只允许查询 Person 资源的角色模板 `my-plugin-role-view-persons`
3. `metadata.name` 的命名规则参考 [metadata name 命名规范](../server/extension.md#metadata-name)。
下面让我们回顾一下这些配置:
`rules` 是个数组,它允许配置多组规则:
- `apiGroups` 对应 `GVK` 中的 `group` 所声明的值。
- `resources` 对应 API 中的 resource 部分。
- `verbs` 表示请求动词,可选值为 "create", "delete", "deletecollection", "get", "list", "patch", "update", "watch"。参考 [Verbs 详解](#verbs)。
`metadata.labels` 中必须包含 `halo.run/role-template: "true"` 以表示它此资源要作为角色模板。
`metadata.annotations` 中:
- `rbac.authorization.halo.run/dependencies`:用于声明角色间的依赖关系,例如管理角色必须要依赖查看角色,以避免分配了管理权限却没有查看权限的情况。
- `rbac.authorization.halo.run/module`:角色模板分组名称。在此示例中,管理 Person 的模板角色将和查看 Person 的模板角色将被在 UI 层面归为一组展示。
- `rbac.authorization.halo.run/display-name`:模板角色的显示名称,用于展示为用户可读的名称信息。
### UI 权限控制 {#ui-permissions}
通过在角色模板的 `metadata.annotations` 中定义 `rbac.authorization.halo.run/ui-permissions` 来控制 UI 权限,这样可以在前端界面通过这个权限来控制菜单或者页面按钮是否展示。
值的规则为 `plugin:{your-plugin-name}:scope-name`, `scope-name` 为你自定义的权限名称,如上面的示例中的 `plugin:my-plugin:person:view``plugin:my-plugin:person:manage`
你可以在 UI 层面使用这个权限来控制菜单是否展示:
```javascript
{
path: "",
name: "HelloWorld",
component: DefaultView,
meta: {
permissions: ["plugin:my-plugin:person:view"]
}
}
```
> 该配置示例为在插件前端部分入口文件 `index.ts`。
或者在按钮或页面组件中使用这个权限来控制是否展示:
```html
<template>
<!-- HasPermission 组件不需要导入,直接使用即可 -->
<HasPermission :permissions="['plugin:my-plugin:person:view']">
<UserFilterDropdown
v-model="selectedUser"
label="用户"
/>
</HasPermission>
</template>
```
### Verbs 详解 {#verbs}
`verbs` 字段用于指定用户或服务在特定资源上能执行的操作类型。这些操作被定义为一组“动词”,每个动词与相应的 HTTP 请求方法相对应。为了更好地理解如何确定合适的 `verbs`,以下是详细的解释和每种动词的具体用途:
动词和对应的 HTTP 方法:
- create: 对应 HTTP 的 POST 方法。用于创建一个新的资源实例,如果是创建子资源且不需要资源名称可以使用 `-` 表示缺省,如 `POST /apis/my-plugin.halo.run/v1alpha1/persons/-/subresource`,同时需要注意 `POST /apis/my-plugin.halo.run/v1alpha1/persons/{some-name}` 不是一个符合规范的 create 操作,创建资源不应该包含资源名称。
- get: 对应 HTTP 的 GET 方法。用于获取单个资源的详细信息,即 API 中包含 resourceName 部分如 `GET /apis/my-plugin.halo.run/v1alpha1/persons/zhangsan`。
- list: 同样对应 HTTP 的 GET 方法,但用于获取资源的集合(列表),这通常涵盖了多个资源实例的摘要或详细信息,如 `GET /apis/my-plugin.halo.run/v1alpha1/persons`。
- watch: 也是对应 HTTP 的 GET 方法。用于实时监控资源或资源集合的变化,通常是通过 WebSocket 连接来实现的,如 `ws://localhost:8090/apis/my-plugin.halo.run/v1alpha1/persons`。
- update: 对应 HTTP 的 PUT 方法。用于更新现有资源的全部内容。
- patch: 对应 HTTP 的 PATCH 方法。用于对现有资源进行部分更新。
- delete: 对应 HTTP 的 DELETE 方法。用于删除单个资源, 即 API 中包含 resourceName 部分如 `DELETE /apis/my-plugin.halo.run/v1alpha1/persons/zhangsan`。
- deletecollection: 同样对应 HTTP 的 DELETE 方法,但用于删除一个资源集合。
可以使用如下表格来简化理解:
| Verb | HTTP Method(s) | Description |
|--------------------|----------------|--------------------------|
| `create` | POST | 创建新资源实例 |
| `get` | GET | 获取单个资源详细信息 |
| `list` | GET | 获取资源列表 |
| `watch` | GET | 监控资源或资源集合的变化 |
| `update` | PUT | 更新现有资源 |
| `patch` | PATCH | 部分更新资源 |
| `delete` | DELETE | 删除单个资源 |
| `deletecollection` | DELETE | 删除资源集合 |
## 默认角色
在 Halo 中,每个访问者都至少有一个角色,包括未登录的用户(被称为匿名用户)它们会拥有角色为 `anonymous` 的角色,而已登录的用户则会至少拥有一个角色名为 `authenticated` 的角色,
但这两个角色不会显示在角色列表中。
`anonymous` 角色的定义参考 [anonymous 角色](https://github.com/halo-dev/halo/blob/main/application/src/main/resources/extensions/role-template-anonymous.yaml)。
`authenticated` 角色的定义参考 [authenticated 角色](https://github.com/halo-dev/halo/blob/main/application/src/main/resources/extensions/role-template-authenticated.yaml)。
进入角色列表页面,你会看到一些内置角色,用于方便你快速的分配权限给用户,并可以基于这些角色来创建新的角色:
- 超级管理员:拥有所有权限,不可删除,不可编辑。
- 访客:拥有默认的 `anonymous` 和 `authenticated` 角色的权限。
- 投稿者:拥有“允许投稿”的权限。
- 作者:拥有“允许管理自己的文章”和”允许发布自己的文章“的权限。
- 文章管理员:拥有“允许管理所有文章”的权限。
## 角色绑定
角色绑定用于将角色中定义的权限授予一个或一组用户。它包含主体列表(用户)以及对所授予角色的引用。
角色绑定示例:
```yaml
apiVersion: v1alpha1
# 这个角色绑定允许 "guqing" 用户拥有 "post-reader" 角色的权限
# 你需要在 Halo 中已经定义了一个名为 "post-reader" 的角色。
kind: RoleBinding
metadata:
name: guqing-post-reader-binding
roleRef:
# "roleRef" 指定了绑定到的角色
apiGroup: ''
# 这里必须是 Role
kind: Role
# 这里的 name 必须匹配到一个已经定义的角色
name: post-reader
subjects:
- apiGroup: ''
kind: User
# 这里的 name 是用户的 username
name: guqing
```
在 Halo 中,当你给一个用户分配角色后,实际上就是创建了一个 ”RoleBinding” 对象来完成的。
## 聚合角色
你可以聚合角色来将多个角色的权限聚合到一个已有的角色中,这样你就不需要再为每个用户分配多个角色了。
聚合角色是通过在你定义的角色模板中添加 `"rbac.authorization.halo.run/aggregate-to-` 开头的 label 来实现的,例如
```yaml
apiVersion: v1alpha1
kind: "Role"
metadata:
name: role-template-view-categories
labels:
halo.run/role-template: "true"
rbac.authorization.halo.run/aggregate-to-editor: "true"
annotations:
rbac.authorization.halo.run/ui-permissions: |
[ "system:categories:view", "uc:categories:view" ]
rules:
- apiGroups: [ "content.halo.run" ]
resources: [ "categories" ]
verbs: [ "get", "list" ]
```
`rbac.authorization.halo.run/aggregate-to-editor` 表示将 `role-template-view-categories` 角色聚合到 `editor` 角色中,这样所有拥有 `editor` 角色的用户都会拥有 `role-template-view-categories` 角色的权限。
如果你想将你写的资源型 APIs 公开给所有用户访问,这时你可以通过聚合角色来将你的资源型 APIs 的角色聚合到 `anonymous` 角色中,这样所有用户都可以访问你的资源型 APIs 了。
```yaml
apiVersion: v1alpha1
kind: Role
metadata:
name: my-plugin-role-view-persons
labels:
halo.run/role-template: "true"
rbac.authorization.halo.run/aggregate-to-anonymous: "true"
annotations:
rbac.authorization.halo.run/module: "Persons Management"
rbac.authorization.halo.run/display-name: "Person Manage"
rbac.authorization.halo.run/ui-permissions: |
["plugin:my-plugin:person:view"]
rules:
- apiGroups: ["my-plugin.halo.run"]
resources: ["my-plugin/persons"]
verbs: ["*"]
```
`rbac.authorization.halo.run/aggregate-to-anonymous` 的写法就表示将 `my-plugin-role-view-persons` 角色聚合到 `anonymous` 角色中。