diff --git a/README.md b/README.md index 6f1d75a..cd9ef9c 100644 --- a/README.md +++ b/README.md @@ -5,16 +5,30 @@ AList 存储库插件,支持创建 AList 类型的存储库 ## 使用方式 首先[部署一个 AList 服务](https://alist.nn.ci/zh/guide/install/docker.html),进入后台管理,创建存储库 + ![](docs/img/1.png) -根据文档填写相关信息,注意这里的挂载路径 + +根据 [AList 文档](https://alist.nn.ci/zh/guide/) 填写相关信息 + ![](docs/img/2.png) + 安装并启用此插件后,在 Halo 后台新建存储策略 + ![](docs/img/3.png) + 选择 AList 存储 + ![](docs/img/4.png) + 根据提示填写以下信息 + ![](docs/img/5.png) + ![](docs/img/6.png) + +你填写的用户应该至少拥有以下权限,这里的基本路径就是挂载路径的上级路径 + +![](docs/img/9.png) ![](docs/img/7.png) ## 注意事项 @@ -26,6 +40,7 @@ AList 存储库插件,支持创建 AList 类型的存储库 client_max_body_size 0; ``` ![](docs/img/8.png) +3. 修改用户状态后需要重新验证来刷新缓存,如:修改用户密码 ## 开发环境 插件开发的详细文档请查阅: @@ -55,14 +70,6 @@ cd path/to/plugin-alist > 此方式需要本地安装 Docker -```bash -# macOS / Linux -./gradlew pnpmInstall - -# Windows -./gradlew.bat pnpmInstall -``` - ```bash # macOS / Linux ./gradlew haloServer diff --git a/build.gradle b/build.gradle index cbc28d8..134ef4d 100644 --- a/build.gradle +++ b/build.gradle @@ -31,7 +31,7 @@ tasks.withType(JavaCompile).configureEach { } halo { - version = '2.17' + version = '2.18' superAdminUsername = 'admin' superAdminPassword = 'admin' externalUrl = 'http://localhost:8090' diff --git a/docs/img/10.png b/docs/img/10.png new file mode 100644 index 0000000..d2fc655 Binary files /dev/null and b/docs/img/10.png differ diff --git a/docs/img/5.png b/docs/img/5.png index 015bb55..04d72fd 100644 Binary files a/docs/img/5.png and b/docs/img/5.png differ diff --git a/docs/img/7.png b/docs/img/7.png index 874d753..f2ab2f2 100644 Binary files a/docs/img/7.png and b/docs/img/7.png differ diff --git a/docs/img/9.png b/docs/img/9.png new file mode 100644 index 0000000..98ef9d9 Binary files /dev/null and b/docs/img/9.png differ diff --git a/gradle.properties b/gradle.properties index aef125e..47a478a 100644 --- a/gradle.properties +++ b/gradle.properties @@ -1 +1 @@ -version=1.0.0 +version=1.0.1 diff --git a/src/main/java/run/halo/alist/config/AListProperties.java b/src/main/java/run/halo/alist/config/AListProperties.java index 1190f89..7972e04 100644 --- a/src/main/java/run/halo/alist/config/AListProperties.java +++ b/src/main/java/run/halo/alist/config/AListProperties.java @@ -22,7 +22,7 @@ public class AListProperties { */ private String site; /** - * AList 挂载路径. + * AList 基本路径下文件夹的路径. */ private String path; /** diff --git a/src/main/java/run/halo/alist/controller/PolicyConfigValidationController.java b/src/main/java/run/halo/alist/controller/PolicyConfigValidationController.java index 968519c..b747061 100644 --- a/src/main/java/run/halo/alist/controller/PolicyConfigValidationController.java +++ b/src/main/java/run/halo/alist/controller/PolicyConfigValidationController.java @@ -1,18 +1,19 @@ package run.halo.alist.controller; -import java.util.Objects; import lombok.RequiredArgsConstructor; import lombok.extern.slf4j.Slf4j; import org.springframework.core.ParameterizedTypeReference; +import org.springframework.http.HttpHeaders; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.RequestBody; import org.springframework.web.bind.annotation.RestController; -import reactor.core.publisher.Flux; import reactor.core.publisher.Mono; -import run.halo.alist.endpoint.AListAttachmentHandler; import run.halo.alist.config.AListProperties; import run.halo.alist.dto.AListResult; -import run.halo.alist.dto.response.AListStorageListRes; +import run.halo.alist.dto.request.AListGetFileInfoReq; +import run.halo.alist.dto.response.AListGetCurrentUserInfoRes; +import run.halo.alist.dto.response.AListGetFileInfoRes; +import run.halo.alist.endpoint.AListAttachmentHandler; import run.halo.alist.exception.AListIllegalArgumentException; import run.halo.app.plugin.ApiVersion; @@ -38,28 +39,68 @@ public Mono validatePolicyConfig(@RequestBody AListProperties properties) .flatMap(token -> handler.getWebClients() .get(properties.getSite()) .get() - .uri("/api/admin/storage/list") - .header("Authorization", token) + .uri("/api/me") + .header(HttpHeaders.AUTHORIZATION, token) .retrieve() .bodyToMono( - new ParameterizedTypeReference>() { + new ParameterizedTypeReference>() { }) .flatMap(response -> { - if (response.getCode().equals("200")) { - return Flux.fromIterable(response.getData().getContent()) - .filter(volume -> Objects.equals(volume.getMountPath(), - properties.getPath())) - .switchIfEmpty(Mono.error(new AListIllegalArgumentException( - "The mount path does not exist"))) - .all(volume -> !volume.isDisabled()) - .filter(isValid -> isValid) - .switchIfEmpty(Mono.error(new AListIllegalArgumentException( - "The storage is disabled"))) - .then(); + if (response.getCode().equals("401")) { + return Mono.error(new AListIllegalArgumentException( + "Current user is disabled")); + } + if (!response.getCode().equals("200")) { + return Mono.error(new AListIllegalArgumentException( + "Wrong Username Or Password")); } - return Mono.error(new AListIllegalArgumentException( - "Wrong Username Or Password")); - })) + AListGetCurrentUserInfoRes userInfo = response.getData(); + return handler.getWebClients() + .get(properties.getSite()) + .post() + .uri("/api/fs/get") + .header(HttpHeaders.AUTHORIZATION, token) + .body(Mono.just(AListGetFileInfoReq.builder() + .path("/") + .build()), + AListGetFileInfoReq.class) + .retrieve() + .bodyToMono( + new ParameterizedTypeReference>() { + }) + .flatMap(res -> { + // 验证当前基本路径是否可用 + if (!res.getCode().equals("200")) { + return Mono.error( + new AListIllegalArgumentException(res.getMessage())); + } + // 管理员用户拥有所有权限 + if (userInfo.getRole() == 2) { + return Mono.empty(); + } + // 普通用户验证权限 + int permission = userInfo.getPermission(); + StringBuilder sb = new StringBuilder(); + if ((permission & 2) == 0) { + sb.append("无需密码访问权限 "); + } + if ((permission & 8) == 0) { + sb.append("创建目录或上传权限 "); + } + if ((permission & 128) == 0) { + sb.append("删除权限 "); + } + if (!sb.isEmpty()) { + sb.append("未开启"); + return Mono.error( + new AListIllegalArgumentException(sb.toString())); + } + return Mono.empty(); + }); + + }) + + ) ); } } diff --git a/src/main/java/run/halo/alist/dto/response/AListGetCurrentUserInfoRes.java b/src/main/java/run/halo/alist/dto/response/AListGetCurrentUserInfoRes.java new file mode 100644 index 0000000..8c25679 --- /dev/null +++ b/src/main/java/run/halo/alist/dto/response/AListGetCurrentUserInfoRes.java @@ -0,0 +1,36 @@ +package run.halo.alist.dto.response; + +import com.fasterxml.jackson.annotation.JsonProperty; +import lombok.AllArgsConstructor; +import lombok.Builder; +import lombok.Data; +import lombok.NoArgsConstructor; + +/** + * @author Roozen + * @version 1.0 + * 2024/8/2 + */ +@Data +@NoArgsConstructor +@AllArgsConstructor +@Builder +public class AListGetCurrentUserInfoRes { + private int id; + private String username; + private String password; + /** + * 基本路径 + */ + @JsonProperty("base_path") + private String basePath; + private int role; + private boolean disabled; + private int permission; + @JsonProperty("sso_id") + private String ssoId; + /** + * 是否开启二步验证 + */ + private boolean otp; +} diff --git a/src/main/java/run/halo/alist/endpoint/AListAttachmentHandler.java b/src/main/java/run/halo/alist/endpoint/AListAttachmentHandler.java index 58b8d35..c967823 100644 --- a/src/main/java/run/halo/alist/endpoint/AListAttachmentHandler.java +++ b/src/main/java/run/halo/alist/endpoint/AListAttachmentHandler.java @@ -24,6 +24,7 @@ import org.springframework.http.codec.multipart.FilePart; import org.springframework.stereotype.Component; import org.springframework.web.reactive.function.client.WebClient; +import org.springframework.web.reactive.function.client.WebClientRequestException; import org.springframework.web.util.UriComponentsBuilder; import org.springframework.web.util.UriUtils; import reactor.core.publisher.Mono; @@ -33,6 +34,7 @@ import run.halo.alist.dto.request.AListGetFileInfoReq; import run.halo.alist.dto.request.AListLoginReq; import run.halo.alist.dto.request.AListRemoveFileReq; +import run.halo.alist.dto.response.AListGetCurrentUserInfoRes; import run.halo.alist.dto.response.AListGetFileInfoRes; import run.halo.alist.dto.response.AListLoginRes; import run.halo.alist.dto.response.AListUploadAsTaskRes; @@ -117,11 +119,8 @@ public Mono upload(UploadContext uploadContext) { var metadata = new Metadata(); metadata.setName(UUID.randomUUID().toString()); metadata.setAnnotations( - Map.of(Constant.EXTERNAL_LINK_ANNO_KEY, - UriUtils.encodePath( - properties.getSite() + "/d" + properties.getPath() + "/" - + file.name(), - StandardCharsets.UTF_8))); + Map.of(Constant.EXTERNAL_LINK_ANNO_KEY, "")); + var spec = new Attachment.AttachmentSpec(); SimpleFilePart simpleFilePart = (SimpleFilePart) file; spec.setDisplayName(simpleFilePart.filename()); @@ -183,7 +182,9 @@ public Mono auth(AListProperties properties) { .retrieve() .bodyToMono( new ParameterizedTypeReference>() { - }); + }) + .onErrorMap(WebClientRequestException.class, + e -> new AListIllegalArgumentException(e.getMessage())); }).flatMap(response -> { if (response.getCode().equals("200")) { log.info("[AList Info] : Login successfully"); @@ -198,7 +199,12 @@ public Mono auth(AListProperties properties) { private AListProperties getProperties(ConfigMap configMap) { var settingJson = configMap.getData().getOrDefault("default", "{}"); - return JsonUtils.jsonToObject(settingJson, AListProperties.class); + AListProperties aListProperties = + JsonUtils.jsonToObject(settingJson, AListProperties.class); + if (aListProperties.getPath().equals("/")) { + aListProperties.setPath(""); + } + return aListProperties; } @Override @@ -264,15 +270,25 @@ public Mono getPermalink(Attachment attachment, Policy policy, ConfigMap co } return Mono.error(new AListRequestErrorException(response.getMessage())); })) - .map(response -> UriComponentsBuilder.fromHttpUrl(properties.getSite()) - .path("/d/{path}/{name}") - .queryParamIfPresent("sign", - Optional.ofNullable(response.getData().getSign()).filter(s -> !s.isEmpty())) - .buildAndExpand( - UriUtils.encodePath(properties.getPath(), StandardCharsets.UTF_8), - UriUtils.encodePath(response.getData().getName(), StandardCharsets.UTF_8) - ) - .toUri()); + .flatMap(response -> webClients.get(properties.getSite()) + .get() + .uri("/api/me") + .header(HttpHeaders.AUTHORIZATION, + tokenCache.getIfPresent(properties.getTokenKey())) + .retrieve() + .bodyToMono( + new ParameterizedTypeReference>() { + }) + .map(res -> UriComponentsBuilder.fromHttpUrl(properties.getSite()) + .path("/d{basePath}{path}/{name}") + .queryParamIfPresent("sign", + Optional.ofNullable(response.getData().getSign()).filter(s -> !s.isEmpty())) + .buildAndExpand( + res.getData().getBasePath(), + properties.getPath(), + response.getData().getName() + ) + .toUri())); } boolean shouldHandle(Policy policy) { diff --git a/src/main/resources/extensions/policy-template-alist.yaml b/src/main/resources/extensions/policy-template-alist.yaml index eb63f97..bfd8947 100644 --- a/src/main/resources/extensions/policy-template-alist.yaml +++ b/src/main/resources/extensions/policy-template-alist.yaml @@ -26,8 +26,7 @@ spec: - $formkit: text name: path label: 挂载路径 - validation: required - help: AList 存储中的挂载路径,必须以 / 开头,如 /aliyun + help: 所填用户基本路径(可在 AList 管理 -> 用户 查看)下文件夹的路径,必须以 / 开头,支持多级目录如 /picture/2024,则全路径为{基本路径}/picture/2024,上传文件时会自动创建不存在的目录,为 / 时留空即可 - $formkit: secret name: secretName required: true