mirror of https://github.com/halo-dev/halo
Suppress compilation warnings and remove deprecated method and classes (#4308)
#### What type of PR is this? /kind cleanup /area core /milestone 2.8.x #### What this PR does / why we need it: - Suppress compilation warnings. - Remove deprecated methods and classes. - Remove unused methods. - Before ```bash ❯ ./gradlew compileJava compileTestJava > Task :application:compileJava /Users/johnniang/workspaces/halo-dev/halo/application/src/main/java/run/halo/app/core/extension/reconciler/ThemeReconciler.java:48: warning: [removal] ThemePathPolicy in run.halo.app.theme has been deprecated and marked for removal private final ThemePathPolicy themePathPolicy; ^ /Users/johnniang/workspaces/halo-dev/halo/application/src/main/java/run/halo/app/core/extension/reconciler/ThemeReconciler.java:48: warning: [removal] ThemePathPolicy in run.halo.app.theme has been deprecated and marked for removal private final ThemePathPolicy themePathPolicy; ^ /Users/johnniang/workspaces/halo-dev/halo/application/src/main/java/run/halo/app/core/extension/reconciler/ThemeReconciler.java:48: warning: [removal] ThemePathPolicy in run.halo.app.theme has been deprecated and marked for removal private final ThemePathPolicy themePathPolicy; ^ /Users/johnniang/workspaces/halo-dev/halo/application/src/main/java/run/halo/app/core/extension/reconciler/ThemeReconciler.java:48: warning: [removal] ThemePathPolicy in run.halo.app.theme has been deprecated and marked for removal private final ThemePathPolicy themePathPolicy; ^ /Users/johnniang/workspaces/halo-dev/halo/application/src/main/java/run/halo/app/core/extension/reconciler/ThemeReconciler.java:48: warning: [removal] ThemePathPolicy in run.halo.app.theme has been deprecated and marked for removal private final ThemePathPolicy themePathPolicy; ^ /Users/johnniang/workspaces/halo-dev/halo/application/src/main/java/run/halo/app/core/extension/reconciler/ThemeReconciler.java:60: warning: [removal] ThemePathPolicy in run.halo.app.theme has been deprecated and marked for removal themePathPolicy = new ThemePathPolicy(haloProperties.getWorkDir()); ^ /Users/johnniang/workspaces/halo-dev/halo/application/src/main/java/run/halo/app/config/WebServerSecurityConfig.java:64: warning: [removal] authorizeExchange() in ServerHttpSecurity has been deprecated and marked for removal .authorizeExchange().anyExchange() ^ /Users/johnniang/workspaces/halo-dev/halo/application/src/main/java/run/halo/app/config/WebServerSecurityConfig.java:65: warning: [removal] and() in ServerHttpSecurity.AuthorizeExchangeSpec has been deprecated and marked for removal .access(new RequestInfoAuthorizationManager(roleService)).and() ^ /Users/johnniang/workspaces/halo-dev/halo/application/src/main/java/run/halo/app/config/WebServerSecurityConfig.java:88: warning: [removal] authorizeExchange() in ServerHttpSecurity has been deprecated and marked for removal .authorizeExchange().anyExchange().permitAll().and() ^ /Users/johnniang/workspaces/halo-dev/halo/application/src/main/java/run/halo/app/config/WebServerSecurityConfig.java:88: warning: [removal] and() in ServerHttpSecurity.AuthorizeExchangeSpec has been deprecated and marked for removal .authorizeExchange().anyExchange().permitAll().and() ^ /Users/johnniang/workspaces/halo-dev/halo/application/src/main/java/run/halo/app/config/WebServerSecurityConfig.java:90: warning: [removal] headers() in ServerHttpSecurity has been deprecated and marked for removal .headers() ^ /Users/johnniang/workspaces/halo-dev/halo/application/src/main/java/run/halo/app/config/WebServerSecurityConfig.java💯 warning: [removal] cache() in ServerHttpSecurity.HeaderSpec has been deprecated and marked for removal .cache().disable().and() ^ /Users/johnniang/workspaces/halo-dev/halo/application/src/main/java/run/halo/app/config/WebServerSecurityConfig.java💯 warning: [removal] and() in ServerHttpSecurity.HeaderSpec has been deprecated and marked for removal .cache().disable().and() ^ /Users/johnniang/workspaces/halo-dev/halo/application/src/main/java/run/halo/app/security/CsrfConfigurer.java:24: warning: [removal] csrf() in ServerHttpSecurity has been deprecated and marked for removal http.csrf().csrfTokenRepository(CookieServerCsrfTokenRepository.withHttpOnlyFalse()) ^ /Users/johnniang/workspaces/halo-dev/halo/application/src/main/java/run/halo/app/security/authorization/DefaultRuleResolver.java:58: warning: [removal] visitRulesFor(UserDetails,RuleAccumulator) in AuthorizationRuleResolver has been deprecated and marked for removal public void visitRulesFor(UserDetails user, RuleAccumulator visitor) { ^ /Users/johnniang/workspaces/halo-dev/halo/application/src/main/java/run/halo/app/security/authorization/DefaultRuleResolver.java:43: warning: [removal] rulesFor(UserDetails) in AuthorizationRuleResolver has been deprecated and marked for removal public PolicyRuleList rulesFor(UserDetails user) { ^ Note: Some input files use or override a deprecated API. Note: Recompile with -Xlint:deprecation for details. Note: /Users/johnniang/workspaces/halo-dev/halo/application/src/main/java/run/halo/app/extension/ReactiveExtensionClientImpl.java uses unchecked or unsafe operations. Note: Recompile with -Xlint:unchecked for details. 16 warnings > Task :application:compileTestJava /Users/johnniang/workspaces/halo-dev/halo/application/src/test/java/run/halo/app/core/extension/reconciler/ThemeReconcilerTest.java:90: warning: [removal] ThemePathPolicy in run.halo.app.theme has been deprecated and marked for removal final ThemePathPolicy themePathPolicy = new ThemePathPolicy(testWorkDir); ^ /Users/johnniang/workspaces/halo-dev/halo/application/src/test/java/run/halo/app/core/extension/reconciler/ThemeReconcilerTest.java:90: warning: [removal] ThemePathPolicy in run.halo.app.theme has been deprecated and marked for removal final ThemePathPolicy themePathPolicy = new ThemePathPolicy(testWorkDir); ^ Note: /Users/johnniang/workspaces/halo-dev/halo/application/src/test/java/run/halo/app/security/authorization/RequestInfoResolverTest.java uses or overrides a deprecated API. Note: Recompile with -Xlint:deprecation for details. Note: /Users/johnniang/workspaces/halo-dev/halo/application/src/test/java/run/halo/app/migration/BackupReconcilerTest.java uses unchecked or unsafe operations. Note: Recompile with -Xlint:unchecked for details. 2 warnings ``` - After ```bash ❯ ./gradlew clean compileJava compileTestJava > Task :api:compileJava /Users/johnniang/workspaces/halo-dev/halo/api/src/main/java/run/halo/app/extension/Unstructured.java:69: warning: This field does not exist, or would have been excluded anyway. @EqualsAndHashCode(exclude = "version") ^ Note: /Users/johnniang/workspaces/halo-dev/halo/api/src/main/java/run/halo/app/extension/Unstructured.java uses unchecked or unsafe operations. Note: Recompile with -Xlint:unchecked for details. 1 warning > Task :application:compileJava Note: /Users/johnniang/workspaces/halo-dev/halo/application/src/main/java/run/halo/app/plugin/SpringExtensionFactory.java uses or overrides a deprecated API. Note: Recompile with -Xlint:deprecation for details. > Task :api:compileTestJava Note: /Users/johnniang/workspaces/halo-dev/halo/api/src/test/java/run/halo/app/infra/utils/JsonUtilsTest.java uses unchecked or unsafe operations. Note: Recompile with -Xlint:unchecked for details. BUILD SUCCESSFUL in 7s 22 actionable tasks: 15 executed, 7 up-to-date ``` #### Does this PR introduce a user-facing change? ```release-note None ```pull/4322/head
parent
f3d7e856ac
commit
150e9975ba
|
@ -1,5 +1,7 @@
|
|||
package run.halo.app.core.extension;
|
||||
|
||||
import static io.swagger.v3.oas.annotations.media.Schema.RequiredMode.REQUIRED;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.ArraySchema;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import java.util.LinkedHashSet;
|
||||
|
@ -15,14 +17,14 @@ import run.halo.app.extension.GVK;
|
|||
@GVK(group = "", version = "v1alpha1", kind = "Menu", plural = "menus", singular = "menu")
|
||||
public class Menu extends AbstractExtension {
|
||||
|
||||
@Schema(description = "The spec of menu.", required = true)
|
||||
@Schema(description = "The spec of menu.", requiredMode = REQUIRED)
|
||||
private Spec spec;
|
||||
|
||||
@Data
|
||||
@Schema(name = "MenuSpec")
|
||||
public static class Spec {
|
||||
|
||||
@Schema(description = "The display name of the menu.", required = true)
|
||||
@Schema(description = "The display name of the menu.", requiredMode = REQUIRED)
|
||||
private String displayName;
|
||||
|
||||
@Schema(description = "Names of menu children below this menu.")
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
package run.halo.app.core.extension;
|
||||
|
||||
import static io.swagger.v3.oas.annotations.media.Schema.RequiredMode.REQUIRED;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonCreator;
|
||||
import com.fasterxml.jackson.annotation.JsonValue;
|
||||
import io.swagger.v3.oas.annotations.media.ArraySchema;
|
||||
|
@ -19,7 +21,7 @@ import run.halo.app.extension.Ref;
|
|||
plural = "menuitems", singular = "menuitem")
|
||||
public class MenuItem extends AbstractExtension {
|
||||
|
||||
@Schema(description = "The spec of menu item.", required = true)
|
||||
@Schema(description = "The spec of menu item.", requiredMode = REQUIRED)
|
||||
private MenuItemSpec spec;
|
||||
|
||||
@Schema(description = "The status of menu item.")
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
package run.halo.app.core.extension;
|
||||
|
||||
import static io.swagger.v3.oas.annotations.media.Schema.RequiredMode.REQUIRED;
|
||||
import static java.util.Arrays.compare;
|
||||
import static run.halo.app.core.extension.Role.GROUP;
|
||||
import static run.halo.app.core.extension.Role.KIND;
|
||||
|
@ -44,7 +45,7 @@ public class Role extends AbstractExtension {
|
|||
public static final String VERSION = "v1alpha1";
|
||||
public static final String KIND = "Role";
|
||||
|
||||
@Schema(required = true)
|
||||
@Schema(requiredMode = REQUIRED)
|
||||
List<PolicyRule> rules;
|
||||
|
||||
/**
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
package run.halo.app.core.extension;
|
||||
|
||||
import static io.swagger.v3.oas.annotations.media.Schema.RequiredMode.REQUIRED;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import java.util.List;
|
||||
import lombok.Data;
|
||||
|
@ -21,25 +23,25 @@ public class Setting extends AbstractExtension {
|
|||
|
||||
public static final String KIND = "Setting";
|
||||
|
||||
@Schema(required = true)
|
||||
@Schema(requiredMode = REQUIRED)
|
||||
private SettingSpec spec;
|
||||
|
||||
@Data
|
||||
public static class SettingSpec {
|
||||
|
||||
@Schema(required = true, minLength = 1)
|
||||
@Schema(requiredMode = REQUIRED, minLength = 1)
|
||||
private List<SettingForm> forms;
|
||||
}
|
||||
|
||||
@Data
|
||||
public static class SettingForm {
|
||||
|
||||
@Schema(required = true)
|
||||
@Schema(requiredMode = REQUIRED)
|
||||
private String group;
|
||||
|
||||
private String label;
|
||||
|
||||
@Schema(required = true)
|
||||
@Schema(requiredMode = REQUIRED)
|
||||
private List<Object> formSchema;
|
||||
}
|
||||
}
|
||||
|
|
|
@ -34,7 +34,7 @@ public class Theme extends AbstractExtension {
|
|||
|
||||
public static final String THEME_NAME_LABEL = "theme.halo.run/theme-name";
|
||||
|
||||
@Schema(required = true)
|
||||
@Schema(requiredMode = REQUIRED)
|
||||
private ThemeSpec spec;
|
||||
|
||||
private ThemeStatus status;
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
package run.halo.app.core.extension;
|
||||
|
||||
import static io.swagger.v3.oas.annotations.media.Schema.RequiredMode.REQUIRED;
|
||||
import static run.halo.app.core.extension.User.GROUP;
|
||||
import static run.halo.app.core.extension.User.KIND;
|
||||
import static run.halo.app.core.extension.User.VERSION;
|
||||
|
@ -41,7 +42,7 @@ public class User extends AbstractExtension {
|
|||
|
||||
public static final String HIDDEN_USER_LABEL = "halo.run/hidden-user";
|
||||
|
||||
@Schema(required = true)
|
||||
@Schema(requiredMode = REQUIRED)
|
||||
private UserSpec spec;
|
||||
|
||||
private UserStatus status;
|
||||
|
@ -49,12 +50,12 @@ public class User extends AbstractExtension {
|
|||
@Data
|
||||
public static class UserSpec {
|
||||
|
||||
@Schema(required = true)
|
||||
@Schema(requiredMode = REQUIRED)
|
||||
private String displayName;
|
||||
|
||||
private String avatar;
|
||||
|
||||
@Schema(required = true)
|
||||
@Schema(requiredMode = REQUIRED)
|
||||
private String email;
|
||||
|
||||
private String phone;
|
||||
|
@ -87,16 +88,16 @@ public class User extends AbstractExtension {
|
|||
@Data
|
||||
public static class LoginHistory {
|
||||
|
||||
@Schema(required = true)
|
||||
@Schema(requiredMode = REQUIRED)
|
||||
private Instant loginAt;
|
||||
|
||||
@Schema(required = true)
|
||||
@Schema(requiredMode = REQUIRED)
|
||||
private String sourceIp;
|
||||
|
||||
@Schema(required = true)
|
||||
@Schema(requiredMode = REQUIRED)
|
||||
private String userAgent;
|
||||
|
||||
@Schema(required = true)
|
||||
@Schema(requiredMode = REQUIRED)
|
||||
private Boolean successful;
|
||||
|
||||
private String reason;
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
package run.halo.app.core.extension.attachment;
|
||||
|
||||
import static io.swagger.v3.oas.annotations.media.Schema.RequiredMode.REQUIRED;
|
||||
import static run.halo.app.core.extension.attachment.Attachment.KIND;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.ArraySchema;
|
||||
|
@ -20,7 +21,7 @@ public class Attachment extends AbstractExtension {
|
|||
|
||||
public static final String KIND = "Attachment";
|
||||
|
||||
@Schema(required = true)
|
||||
@Schema(requiredMode = REQUIRED)
|
||||
private AttachmentSpec spec;
|
||||
|
||||
private AttachmentStatus status;
|
||||
|
|
|
@ -20,8 +20,6 @@ public enum Constant {
|
|||
* Do not use this key to set external link. You could implement
|
||||
* {@link AttachmentHandler#getPermalink} by your self.
|
||||
* <p>
|
||||
*
|
||||
* @deprecated Use your own group instead.
|
||||
*/
|
||||
public static final String EXTERNAL_LINK_ANNO_KEY = GROUP + "/external-link";
|
||||
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
package run.halo.app.core.extension.attachment;
|
||||
|
||||
import static io.swagger.v3.oas.annotations.media.Schema.RequiredMode.REQUIRED;
|
||||
import static run.halo.app.core.extension.attachment.Group.KIND;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
|
@ -20,7 +21,7 @@ public class Group extends AbstractExtension {
|
|||
public static final String KIND = "Group";
|
||||
public static final String HIDDEN_LABEL = "halo.run/hidden";
|
||||
|
||||
@Schema(required = true)
|
||||
@Schema(requiredMode = REQUIRED)
|
||||
private GroupSpec spec;
|
||||
|
||||
private GroupStatus status;
|
||||
|
@ -28,7 +29,7 @@ public class Group extends AbstractExtension {
|
|||
@Data
|
||||
public static class GroupSpec {
|
||||
|
||||
@Schema(required = true, description = "Display name of group")
|
||||
@Schema(requiredMode = REQUIRED, description = "Display name of group")
|
||||
private String displayName;
|
||||
|
||||
}
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
package run.halo.app.core.extension.content;
|
||||
|
||||
import static io.swagger.v3.oas.annotations.media.Schema.RequiredMode.REQUIRED;
|
||||
import static run.halo.app.core.extension.content.Category.KIND;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
|
@ -28,7 +29,7 @@ public class Category extends AbstractExtension {
|
|||
|
||||
public static final GroupVersionKind GVK = GroupVersionKind.fromExtension(Category.class);
|
||||
|
||||
@Schema(required = true)
|
||||
@Schema(requiredMode = REQUIRED)
|
||||
private CategorySpec spec;
|
||||
|
||||
@Schema
|
||||
|
@ -42,10 +43,10 @@ public class Category extends AbstractExtension {
|
|||
@Data
|
||||
public static class CategorySpec {
|
||||
|
||||
@Schema(required = true, minLength = 1)
|
||||
@Schema(requiredMode = REQUIRED, minLength = 1)
|
||||
private String displayName;
|
||||
|
||||
@Schema(required = true, minLength = 1)
|
||||
@Schema(requiredMode = REQUIRED, minLength = 1)
|
||||
private String slug;
|
||||
|
||||
private String description;
|
||||
|
@ -54,7 +55,7 @@ public class Category extends AbstractExtension {
|
|||
|
||||
private String template;
|
||||
|
||||
@Schema(required = true, defaultValue = "0")
|
||||
@Schema(requiredMode = REQUIRED, defaultValue = "0")
|
||||
private Integer priority;
|
||||
|
||||
private List<String> children;
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
package run.halo.app.core.extension.content;
|
||||
|
||||
import static io.swagger.v3.oas.annotations.media.Schema.RequiredMode.REQUIRED;
|
||||
import static org.apache.commons.lang3.ObjectUtils.defaultIfNull;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
|
@ -30,7 +31,7 @@ public class Comment extends AbstractExtension {
|
|||
|
||||
public static final String KIND = "Comment";
|
||||
|
||||
@Schema(required = true)
|
||||
@Schema(requiredMode = REQUIRED)
|
||||
private CommentSpec spec;
|
||||
|
||||
@Schema
|
||||
|
@ -49,7 +50,7 @@ public class Comment extends AbstractExtension {
|
|||
@EqualsAndHashCode(callSuper = true)
|
||||
public static class CommentSpec extends BaseCommentSpec {
|
||||
|
||||
@Schema(required = true)
|
||||
@Schema(requiredMode = REQUIRED)
|
||||
private Ref subjectRef;
|
||||
|
||||
private Instant lastReadTime;
|
||||
|
@ -58,13 +59,13 @@ public class Comment extends AbstractExtension {
|
|||
@Data
|
||||
public static class BaseCommentSpec {
|
||||
|
||||
@Schema(required = true, minLength = 1)
|
||||
@Schema(requiredMode = REQUIRED, minLength = 1)
|
||||
private String raw;
|
||||
|
||||
@Schema(required = true, minLength = 1)
|
||||
@Schema(requiredMode = REQUIRED, minLength = 1)
|
||||
private String content;
|
||||
|
||||
@Schema(required = true)
|
||||
@Schema(requiredMode = REQUIRED)
|
||||
private CommentOwner owner;
|
||||
|
||||
private String userAgent;
|
||||
|
@ -78,19 +79,19 @@ public class Comment extends AbstractExtension {
|
|||
*/
|
||||
private Instant creationTime;
|
||||
|
||||
@Schema(required = true, defaultValue = "0")
|
||||
@Schema(requiredMode = REQUIRED, defaultValue = "0")
|
||||
private Integer priority;
|
||||
|
||||
@Schema(required = true, defaultValue = "false")
|
||||
@Schema(requiredMode = REQUIRED, defaultValue = "false")
|
||||
private Boolean top;
|
||||
|
||||
@Schema(required = true, defaultValue = "true")
|
||||
@Schema(requiredMode = REQUIRED, defaultValue = "true")
|
||||
private Boolean allowNotification;
|
||||
|
||||
@Schema(required = true, defaultValue = "false")
|
||||
@Schema(requiredMode = REQUIRED, defaultValue = "false")
|
||||
private Boolean approved;
|
||||
|
||||
@Schema(required = true, defaultValue = "false")
|
||||
@Schema(requiredMode = REQUIRED, defaultValue = "false")
|
||||
private Boolean hidden;
|
||||
}
|
||||
|
||||
|
@ -100,10 +101,10 @@ public class Comment extends AbstractExtension {
|
|||
public static final String AVATAR_ANNO = "avatar";
|
||||
public static final String WEBSITE_ANNO = "website";
|
||||
|
||||
@Schema(required = true, minLength = 1)
|
||||
@Schema(requiredMode = REQUIRED, minLength = 1)
|
||||
private String kind;
|
||||
|
||||
@Schema(required = true, maxLength = 64)
|
||||
@Schema(requiredMode = REQUIRED, maxLength = 64)
|
||||
private String name;
|
||||
|
||||
private String displayName;
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
package run.halo.app.core.extension.content;
|
||||
|
||||
import static io.swagger.v3.oas.annotations.media.Schema.RequiredMode.REQUIRED;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
@ -21,14 +23,14 @@ public class Reply extends AbstractExtension {
|
|||
|
||||
public static final String KIND = "Reply";
|
||||
|
||||
@Schema(required = true)
|
||||
@Schema(requiredMode = REQUIRED)
|
||||
private ReplySpec spec;
|
||||
|
||||
@Data
|
||||
@EqualsAndHashCode(callSuper = true)
|
||||
public static class ReplySpec extends Comment.BaseCommentSpec {
|
||||
|
||||
@Schema(required = true, minLength = 1)
|
||||
@Schema(requiredMode = REQUIRED, minLength = 1)
|
||||
private String commentName;
|
||||
|
||||
private String quoteReply;
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
package run.halo.app.core.extension.content;
|
||||
|
||||
import static io.swagger.v3.oas.annotations.media.Schema.RequiredMode.REQUIRED;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import java.time.Instant;
|
||||
|
@ -36,7 +38,7 @@ public class SinglePage extends AbstractExtension {
|
|||
public static final String OWNER_LABEL = "content.halo.run/owner";
|
||||
public static final String VISIBLE_LABEL = "content.halo.run/visible";
|
||||
|
||||
@Schema(required = true)
|
||||
@Schema(requiredMode = REQUIRED)
|
||||
private SinglePageSpec spec;
|
||||
|
||||
@Schema
|
||||
|
@ -58,10 +60,10 @@ public class SinglePage extends AbstractExtension {
|
|||
|
||||
@Data
|
||||
public static class SinglePageSpec {
|
||||
@Schema(required = true, minLength = 1)
|
||||
@Schema(requiredMode = REQUIRED, minLength = 1)
|
||||
private String title;
|
||||
|
||||
@Schema(required = true, minLength = 1)
|
||||
@Schema(requiredMode = REQUIRED, minLength = 1)
|
||||
private String slug;
|
||||
|
||||
/**
|
||||
|
@ -79,27 +81,27 @@ public class SinglePage extends AbstractExtension {
|
|||
|
||||
private String cover;
|
||||
|
||||
@Schema(required = true, defaultValue = "false")
|
||||
@Schema(requiredMode = REQUIRED, defaultValue = "false")
|
||||
private Boolean deleted;
|
||||
|
||||
@Schema(required = true, defaultValue = "false")
|
||||
@Schema(requiredMode = REQUIRED, defaultValue = "false")
|
||||
private Boolean publish;
|
||||
|
||||
private Instant publishTime;
|
||||
|
||||
@Schema(required = true, defaultValue = "false")
|
||||
@Schema(requiredMode = REQUIRED, defaultValue = "false")
|
||||
private Boolean pinned;
|
||||
|
||||
@Schema(required = true, defaultValue = "true")
|
||||
@Schema(requiredMode = REQUIRED, defaultValue = "true")
|
||||
private Boolean allowComment;
|
||||
|
||||
@Schema(required = true, defaultValue = "PUBLIC")
|
||||
@Schema(requiredMode = REQUIRED, defaultValue = "PUBLIC")
|
||||
private Post.VisibleEnum visible;
|
||||
|
||||
@Schema(required = true, defaultValue = "0")
|
||||
@Schema(requiredMode = REQUIRED, defaultValue = "0")
|
||||
private Integer priority;
|
||||
|
||||
@Schema(required = true)
|
||||
@Schema(requiredMode = REQUIRED)
|
||||
private Post.Excerpt excerpt;
|
||||
|
||||
private List<Map<String, String>> htmlMetas;
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
package run.halo.app.core.extension.content;
|
||||
|
||||
import static io.swagger.v3.oas.annotations.media.Schema.RequiredMode.REQUIRED;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import java.time.Instant;
|
||||
import java.util.LinkedHashSet;
|
||||
|
@ -26,19 +28,19 @@ public class Snapshot extends AbstractExtension {
|
|||
public static final String KIND = "Snapshot";
|
||||
public static final String KEEP_RAW_ANNO = "content.halo.run/keep-raw";
|
||||
|
||||
@Schema(required = true)
|
||||
@Schema(requiredMode = REQUIRED)
|
||||
private SnapShotSpec spec;
|
||||
|
||||
@Data
|
||||
public static class SnapShotSpec {
|
||||
|
||||
@Schema(required = true)
|
||||
@Schema(requiredMode = REQUIRED)
|
||||
private Ref subjectRef;
|
||||
|
||||
/**
|
||||
* such as: markdown | html | json | asciidoc | latex.
|
||||
*/
|
||||
@Schema(required = true, minLength = 1, maxLength = 50)
|
||||
@Schema(requiredMode = REQUIRED, minLength = 1, maxLength = 50)
|
||||
private String rawType;
|
||||
|
||||
private String rawPatch;
|
||||
|
@ -49,7 +51,7 @@ public class Snapshot extends AbstractExtension {
|
|||
|
||||
private Instant lastModifyTime;
|
||||
|
||||
@Schema(required = true, minLength = 1)
|
||||
@Schema(requiredMode = REQUIRED, minLength = 1)
|
||||
private String owner;
|
||||
|
||||
private Set<String> contributors;
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
package run.halo.app.core.extension.content;
|
||||
|
||||
import static io.swagger.v3.oas.annotations.media.Schema.RequiredMode.REQUIRED;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
|
@ -25,7 +27,7 @@ public class Tag extends AbstractExtension {
|
|||
|
||||
public static final GroupVersionKind GVK = GroupVersionKind.fromExtension(Tag.class);
|
||||
|
||||
@Schema(required = true)
|
||||
@Schema(requiredMode = REQUIRED)
|
||||
private TagSpec spec;
|
||||
|
||||
@Schema
|
||||
|
@ -34,10 +36,10 @@ public class Tag extends AbstractExtension {
|
|||
@Data
|
||||
public static class TagSpec {
|
||||
|
||||
@Schema(required = true, minLength = 1)
|
||||
@Schema(requiredMode = REQUIRED, minLength = 1)
|
||||
private String displayName;
|
||||
|
||||
@Schema(required = true, minLength = 1)
|
||||
@Schema(requiredMode = REQUIRED, minLength = 1)
|
||||
private String slug;
|
||||
|
||||
/**
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
package run.halo.app.extension;
|
||||
|
||||
import static io.swagger.v3.oas.annotations.media.Schema.RequiredMode.REQUIRED;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
|
@ -13,7 +15,7 @@ import org.springframework.util.StringUtils;
|
|||
*/
|
||||
public interface ExtensionOperator {
|
||||
|
||||
@Schema(required = true)
|
||||
@Schema(requiredMode = REQUIRED)
|
||||
@JsonProperty("apiVersion")
|
||||
default String getApiVersion() {
|
||||
final var gvk = getClass().getAnnotation(GVK.class);
|
||||
|
@ -27,7 +29,7 @@ public interface ExtensionOperator {
|
|||
return gvk.version();
|
||||
}
|
||||
|
||||
@Schema(required = true)
|
||||
@Schema(requiredMode = REQUIRED)
|
||||
@JsonProperty("kind")
|
||||
default String getKind() {
|
||||
final var gvk = getClass().getAnnotation(GVK.class);
|
||||
|
@ -38,7 +40,7 @@ public interface ExtensionOperator {
|
|||
return gvk.kind();
|
||||
}
|
||||
|
||||
@Schema(required = true, implementation = Metadata.class)
|
||||
@Schema(requiredMode = REQUIRED, implementation = Metadata.class)
|
||||
@JsonProperty("metadata")
|
||||
MetadataOperator getMetadata();
|
||||
|
||||
|
@ -48,30 +50,6 @@ public interface ExtensionOperator {
|
|||
|
||||
void setMetadata(MetadataOperator metadata);
|
||||
|
||||
/**
|
||||
* This method is only for backward compatibility. Same as {@link #getMetadata()}.
|
||||
*
|
||||
* @return Extension metadata.
|
||||
* @see #getMetadata()
|
||||
*/
|
||||
@JsonIgnore
|
||||
@Deprecated(forRemoval = true)
|
||||
default MetadataOperator metadata() {
|
||||
return getMetadata();
|
||||
}
|
||||
|
||||
/**
|
||||
* This method is only for backward compatibility. Same as
|
||||
* {@link #setMetadata(MetadataOperator)}.
|
||||
*
|
||||
* @param metadata is Extension metadata.
|
||||
* @see #setMetadata(MetadataOperator)
|
||||
*/
|
||||
@Deprecated(forRemoval = true)
|
||||
default void metadata(MetadataOperator metadata) {
|
||||
setMetadata(metadata);
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets GroupVersionKind of the Extension.
|
||||
*
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
package run.halo.app.extension;
|
||||
|
||||
import static io.swagger.v3.oas.annotations.media.Schema.RequiredMode.REQUIRED;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import java.util.ArrayList;
|
||||
|
@ -16,17 +18,17 @@ import run.halo.app.infra.utils.GenericClassUtils;
|
|||
public class ListResult<T> implements Iterable<T>, Supplier<Stream<T>> {
|
||||
|
||||
@Schema(description = "Page number, starts from 1. If not set or equal to 0, it means no "
|
||||
+ "pagination.", required = true)
|
||||
+ "pagination.", requiredMode = REQUIRED)
|
||||
private final int page;
|
||||
|
||||
@Schema(description = "Size of each page. If not set or equal to 0, it means no pagination.",
|
||||
required = true)
|
||||
requiredMode = REQUIRED)
|
||||
private final int size;
|
||||
|
||||
@Schema(description = "Total elements.", required = true)
|
||||
@Schema(description = "Total elements.", requiredMode = REQUIRED)
|
||||
private final long total;
|
||||
|
||||
@Schema(description = "A chunk of items.", required = true)
|
||||
@Schema(description = "A chunk of items.", requiredMode = REQUIRED)
|
||||
private final List<T> items;
|
||||
|
||||
public ListResult(int page, int size, long total, List<T> items) {
|
||||
|
@ -50,17 +52,20 @@ public class ListResult<T> implements Iterable<T>, Supplier<Stream<T>> {
|
|||
this(0, 0, items.size(), items);
|
||||
}
|
||||
|
||||
@Schema(description = "Indicates whether current page is the first page.", required = true)
|
||||
@Schema(description = "Indicates whether current page is the first page.",
|
||||
requiredMode = REQUIRED)
|
||||
public boolean isFirst() {
|
||||
return !hasPrevious();
|
||||
}
|
||||
|
||||
@Schema(description = "Indicates whether current page is the last page.", required = true)
|
||||
@Schema(description = "Indicates whether current page is the last page.",
|
||||
requiredMode = REQUIRED)
|
||||
public boolean isLast() {
|
||||
return !hasNext();
|
||||
}
|
||||
|
||||
@Schema(description = "Indicates whether current page has previous page.", required = true)
|
||||
@Schema(description = "Indicates whether current page has previous page.",
|
||||
requiredMode = REQUIRED)
|
||||
@JsonProperty("hasNext")
|
||||
public boolean hasNext() {
|
||||
if (page <= 0) {
|
||||
|
@ -69,7 +74,8 @@ public class ListResult<T> implements Iterable<T>, Supplier<Stream<T>> {
|
|||
return page < getTotalPages();
|
||||
}
|
||||
|
||||
@Schema(description = "Indicates whether current page has previous page.", required = true)
|
||||
@Schema(description = "Indicates whether current page has previous page.",
|
||||
requiredMode = REQUIRED)
|
||||
@JsonProperty("hasPrevious")
|
||||
public boolean hasPrevious() {
|
||||
return page > 1;
|
||||
|
@ -80,7 +86,7 @@ public class ListResult<T> implements Iterable<T>, Supplier<Stream<T>> {
|
|||
return items.iterator();
|
||||
}
|
||||
|
||||
@Schema(description = "Indicates total pages.", required = true)
|
||||
@Schema(description = "Indicates total pages.", requiredMode = REQUIRED)
|
||||
@JsonProperty("totalPages")
|
||||
public long getTotalPages() {
|
||||
return size == 0 ? 1 : (total + size - 1) / size;
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
package run.halo.app.extension;
|
||||
|
||||
import static io.swagger.v3.oas.annotations.media.Schema.RequiredMode.REQUIRED;
|
||||
|
||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
|
@ -17,7 +19,7 @@ import java.util.Set;
|
|||
@Schema(implementation = Metadata.class)
|
||||
public interface MetadataOperator {
|
||||
|
||||
@Schema(name = "name", description = "Metadata name", required = true)
|
||||
@Schema(name = "name", description = "Metadata name", requiredMode = REQUIRED)
|
||||
@JsonProperty("name")
|
||||
String getName();
|
||||
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
package run.halo.app.extension;
|
||||
|
||||
import static io.swagger.v3.oas.annotations.media.Schema.RequiredMode.REQUIRED;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import java.util.Objects;
|
||||
import lombok.Data;
|
||||
|
@ -17,7 +19,7 @@ public class Ref {
|
|||
@Schema(description = "Extension kind")
|
||||
private String kind;
|
||||
|
||||
@Schema(required = true, description = "Extension name. This field is mandatory")
|
||||
@Schema(requiredMode = REQUIRED, description = "Extension name. This field is mandatory")
|
||||
private String name;
|
||||
|
||||
public static Ref of(String name) {
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
package run.halo.app.infra;
|
||||
|
||||
import static io.swagger.v3.oas.annotations.media.Schema.RequiredMode.REQUIRED;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import java.time.Instant;
|
||||
import lombok.AllArgsConstructor;
|
||||
|
@ -28,7 +30,7 @@ public class Condition {
|
|||
* example: Ready, Initialized.
|
||||
* maxLength: 316.
|
||||
*/
|
||||
@Schema(required = true, maxLength = 316,
|
||||
@Schema(requiredMode = REQUIRED, maxLength = 316,
|
||||
pattern = "^([a-z0-9]([-a-z0-9]*[a-z0-9])?(\\.[a-z0-9]([-a-z0-9]*[a-z0-9])?)*/)?("
|
||||
+ "([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$")
|
||||
private String type;
|
||||
|
@ -36,26 +38,26 @@ public class Condition {
|
|||
/**
|
||||
* Status is the status of the condition. Can be True, False, Unknown.
|
||||
*/
|
||||
@Schema(required = true)
|
||||
@Schema(requiredMode = REQUIRED)
|
||||
private ConditionStatus status;
|
||||
|
||||
/**
|
||||
* Last time the condition transitioned from one status to another.
|
||||
*/
|
||||
@Schema(required = true)
|
||||
@Schema(requiredMode = REQUIRED)
|
||||
private Instant lastTransitionTime;
|
||||
|
||||
/**
|
||||
* Human-readable message indicating details about last transition.
|
||||
* This may be an empty string.
|
||||
*/
|
||||
@Schema(required = true, maxLength = 32768)
|
||||
@Schema(requiredMode = REQUIRED, maxLength = 32768)
|
||||
private String message;
|
||||
|
||||
/**
|
||||
* Unique, one-word, CamelCase reason for the condition's last transition.
|
||||
*/
|
||||
@Schema(required = true, maxLength = 1024,
|
||||
@Schema(requiredMode = REQUIRED, maxLength = 1024,
|
||||
pattern = "^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$")
|
||||
private String reason;
|
||||
}
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
package run.halo.app.search;
|
||||
|
||||
import static io.swagger.v3.oas.annotations.media.Schema.RequiredMode.REQUIRED;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import org.springframework.util.MultiValueMap;
|
||||
import org.springframework.util.StringUtils;
|
||||
|
@ -17,7 +19,7 @@ public class SearchParam {
|
|||
this.query = query;
|
||||
}
|
||||
|
||||
@Schema(name = "keyword", required = true)
|
||||
@Schema(name = "keyword", requiredMode = REQUIRED)
|
||||
public String getKeyword() {
|
||||
var keyword = query.getFirst("keyword");
|
||||
if (!StringUtils.hasText(keyword)) {
|
||||
|
|
|
@ -61,8 +61,9 @@ public class WebServerSecurityConfig {
|
|||
|
||||
http.securityMatcher(pathMatchers("/api/**", "/apis/**", "/oauth2/**",
|
||||
"/login/**", "/logout", "/actuator/**"))
|
||||
.authorizeExchange().anyExchange()
|
||||
.access(new RequestInfoAuthorizationManager(roleService)).and()
|
||||
.authorizeExchange(spec -> {
|
||||
spec.anyExchange().access(new RequestInfoAuthorizationManager(roleService));
|
||||
})
|
||||
.anonymous(spec -> {
|
||||
spec.authorities(AnonymousUserConst.Role);
|
||||
spec.principal(AnonymousUserConst.PRINCIPAL);
|
||||
|
@ -85,19 +86,24 @@ public class WebServerSecurityConfig {
|
|||
var mediaTypeMatcher = new MediaTypeServerWebExchangeMatcher(MediaType.TEXT_HTML);
|
||||
mediaTypeMatcher.setIgnoredMediaTypes(Set.of(MediaType.ALL));
|
||||
http.securityMatcher(new AndServerWebExchangeMatcher(pathMatcher, mediaTypeMatcher))
|
||||
.authorizeExchange().anyExchange().permitAll().and()
|
||||
.securityContextRepository(securityContextRepository)
|
||||
.headers()
|
||||
.frameOptions(spec -> {
|
||||
var frameOptions = haloProperties.getSecurity().getFrameOptions();
|
||||
spec.mode(frameOptions.getMode());
|
||||
if (frameOptions.isDisabled()) {
|
||||
spec.disable();
|
||||
}
|
||||
.authorizeExchange(spec -> {
|
||||
spec.anyExchange().permitAll();
|
||||
})
|
||||
.referrerPolicy(
|
||||
spec -> spec.policy(haloProperties.getSecurity().getReferrerOptions().getPolicy()))
|
||||
.cache().disable().and()
|
||||
.headers(headerSpec -> headerSpec
|
||||
.frameOptions(frameSpec -> {
|
||||
var frameOptions = haloProperties.getSecurity().getFrameOptions();
|
||||
frameSpec.mode(frameOptions.getMode());
|
||||
if (frameOptions.isDisabled()) {
|
||||
frameSpec.disable();
|
||||
}
|
||||
})
|
||||
.referrerPolicy(referrerPolicySpec -> {
|
||||
referrerPolicySpec.policy(
|
||||
haloProperties.getSecurity().getReferrerOptions().getPolicy());
|
||||
})
|
||||
.cache(ServerHttpSecurity.HeaderSpec.CacheSpec::disable)
|
||||
)
|
||||
.anonymous(spec -> spec.authenticationFilter(
|
||||
new HaloAnonymousAuthenticationWebFilter("portal", AnonymousUserConst.PRINCIPAL,
|
||||
AuthorityUtils.createAuthorityList(AnonymousUserConst.Role),
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
package run.halo.app.content;
|
||||
|
||||
import static io.swagger.v3.oas.annotations.media.Schema.RequiredMode.REQUIRED;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import java.util.HashMap;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
|
@ -11,11 +13,11 @@ import run.halo.app.extension.Ref;
|
|||
* @author guqing
|
||||
* @since 2.0.0
|
||||
*/
|
||||
public record ContentRequest(@Schema(required = true) Ref subjectRef,
|
||||
public record ContentRequest(@Schema(requiredMode = REQUIRED) Ref subjectRef,
|
||||
String headSnapshotName,
|
||||
@Schema(required = true) String raw,
|
||||
@Schema(required = true) String content,
|
||||
@Schema(required = true) String rawType) {
|
||||
@Schema(requiredMode = REQUIRED) String raw,
|
||||
@Schema(requiredMode = REQUIRED) String content,
|
||||
@Schema(requiredMode = REQUIRED) String rawType) {
|
||||
|
||||
public Snapshot toSnapshot() {
|
||||
final Snapshot snapshot = new Snapshot();
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
package run.halo.app.content;
|
||||
|
||||
import static io.swagger.v3.oas.annotations.media.Schema.RequiredMode.REQUIRED;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import java.util.List;
|
||||
import lombok.Data;
|
||||
|
@ -17,21 +19,21 @@ import run.halo.app.core.extension.content.Tag;
|
|||
@Data
|
||||
public class ListedPost {
|
||||
|
||||
@Schema(required = true)
|
||||
@Schema(requiredMode = REQUIRED)
|
||||
private Post post;
|
||||
|
||||
@Schema(required = true)
|
||||
@Schema(requiredMode = REQUIRED)
|
||||
private List<Category> categories;
|
||||
|
||||
@Schema(required = true)
|
||||
@Schema(requiredMode = REQUIRED)
|
||||
private List<Tag> tags;
|
||||
|
||||
@Schema(required = true)
|
||||
@Schema(requiredMode = REQUIRED)
|
||||
private List<Contributor> contributors;
|
||||
|
||||
@Schema(required = true)
|
||||
@Schema(requiredMode = REQUIRED)
|
||||
private Contributor owner;
|
||||
|
||||
@Schema(required = true)
|
||||
@Schema(requiredMode = REQUIRED)
|
||||
private Stats stats;
|
||||
}
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
package run.halo.app.content;
|
||||
|
||||
import static io.swagger.v3.oas.annotations.media.Schema.RequiredMode.REQUIRED;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import java.util.List;
|
||||
import lombok.Data;
|
||||
|
@ -15,15 +17,15 @@ import run.halo.app.core.extension.content.SinglePage;
|
|||
@Data
|
||||
public class ListedSinglePage {
|
||||
|
||||
@Schema(required = true)
|
||||
@Schema(requiredMode = REQUIRED)
|
||||
private SinglePage page;
|
||||
|
||||
@Schema(required = true)
|
||||
@Schema(requiredMode = REQUIRED)
|
||||
private List<Contributor> contributors;
|
||||
|
||||
@Schema(required = true)
|
||||
@Schema(requiredMode = REQUIRED)
|
||||
private Contributor owner;
|
||||
|
||||
@Schema(required = true)
|
||||
@Schema(requiredMode = REQUIRED)
|
||||
private Stats stats;
|
||||
}
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
package run.halo.app.content;
|
||||
|
||||
import static io.swagger.v3.oas.annotations.media.Schema.RequiredMode.REQUIRED;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import run.halo.app.core.extension.content.Post;
|
||||
import run.halo.app.extension.Ref;
|
||||
|
@ -8,8 +10,8 @@ import run.halo.app.extension.Ref;
|
|||
* @author guqing
|
||||
* @since 2.0.0
|
||||
*/
|
||||
public record PostRequest(@Schema(required = true) Post post,
|
||||
@Schema(required = true) Content content) {
|
||||
public record PostRequest(@Schema(requiredMode = REQUIRED) Post post,
|
||||
@Schema(requiredMode = REQUIRED) Content content) {
|
||||
|
||||
public ContentRequest contentRequest() {
|
||||
Ref subjectRef = Ref.of(post);
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
package run.halo.app.content;
|
||||
|
||||
import static io.swagger.v3.oas.annotations.media.Schema.RequiredMode.REQUIRED;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import run.halo.app.core.extension.content.SinglePage;
|
||||
import run.halo.app.extension.Ref;
|
||||
|
@ -10,8 +12,8 @@ import run.halo.app.extension.Ref;
|
|||
* @author guqing
|
||||
* @since 2.0.0
|
||||
*/
|
||||
public record SinglePageRequest(@Schema(required = true) SinglePage page,
|
||||
@Schema(required = true) Content content) {
|
||||
public record SinglePageRequest(@Schema(requiredMode = REQUIRED) SinglePage page,
|
||||
@Schema(requiredMode = REQUIRED) Content content) {
|
||||
|
||||
public ContentRequest contentRequest() {
|
||||
Ref subjectRef = Ref.of(page);
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
package run.halo.app.content.comment;
|
||||
|
||||
import static io.swagger.v3.oas.annotations.media.Schema.RequiredMode.REQUIRED;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import java.util.UUID;
|
||||
import lombok.Data;
|
||||
|
@ -16,15 +18,15 @@ import run.halo.app.extension.Ref;
|
|||
@Data
|
||||
public class CommentRequest {
|
||||
|
||||
@Schema(required = true)
|
||||
@Schema(requiredMode = REQUIRED)
|
||||
private Ref subjectRef;
|
||||
|
||||
private CommentEmailOwner owner;
|
||||
|
||||
@Schema(required = true, minLength = 1)
|
||||
@Schema(requiredMode = REQUIRED, minLength = 1)
|
||||
private String raw;
|
||||
|
||||
@Schema(required = true, minLength = 1)
|
||||
@Schema(requiredMode = REQUIRED, minLength = 1)
|
||||
private String content;
|
||||
|
||||
@Schema(defaultValue = "false")
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
package run.halo.app.content.comment;
|
||||
|
||||
import static io.swagger.v3.oas.annotations.media.Schema.RequiredMode.REQUIRED;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
|
@ -16,14 +18,14 @@ import run.halo.app.extension.Extension;
|
|||
@Builder
|
||||
public class ListedComment {
|
||||
|
||||
@Schema(required = true)
|
||||
@Schema(requiredMode = REQUIRED)
|
||||
private Comment comment;
|
||||
|
||||
@Schema(required = true)
|
||||
@Schema(requiredMode = REQUIRED)
|
||||
private OwnerInfo owner;
|
||||
|
||||
private Extension subject;
|
||||
|
||||
@Schema(required = true)
|
||||
@Schema(requiredMode = REQUIRED)
|
||||
private CommentStats stats;
|
||||
}
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
package run.halo.app.content.comment;
|
||||
|
||||
import static io.swagger.v3.oas.annotations.media.Schema.RequiredMode.REQUIRED;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
|
@ -15,12 +17,12 @@ import run.halo.app.core.extension.content.Reply;
|
|||
@Builder
|
||||
public class ListedReply {
|
||||
|
||||
@Schema(required = true)
|
||||
@Schema(requiredMode = REQUIRED)
|
||||
private Reply reply;
|
||||
|
||||
@Schema(required = true)
|
||||
@Schema(requiredMode = REQUIRED)
|
||||
private OwnerInfo owner;
|
||||
|
||||
@Schema(required = true)
|
||||
@Schema(requiredMode = REQUIRED)
|
||||
private CommentStats stats;
|
||||
}
|
||||
|
|
|
@ -1,5 +1,7 @@
|
|||
package run.halo.app.content.comment;
|
||||
|
||||
import static io.swagger.v3.oas.annotations.media.Schema.RequiredMode.REQUIRED;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import java.util.UUID;
|
||||
import lombok.Data;
|
||||
|
@ -15,10 +17,10 @@ import run.halo.app.extension.Metadata;
|
|||
@Data
|
||||
public class ReplyRequest {
|
||||
|
||||
@Schema(required = true, minLength = 1)
|
||||
@Schema(requiredMode = REQUIRED, minLength = 1)
|
||||
private String raw;
|
||||
|
||||
@Schema(required = true, minLength = 1)
|
||||
@Schema(requiredMode = REQUIRED, minLength = 1)
|
||||
private String content;
|
||||
|
||||
@Schema(defaultValue = "false")
|
||||
|
|
|
@ -1,5 +1,6 @@
|
|||
package run.halo.app.core.extension.attachment.endpoint;
|
||||
|
||||
import static io.swagger.v3.oas.annotations.media.Schema.RequiredMode.REQUIRED;
|
||||
import static java.util.Comparator.comparing;
|
||||
import static org.springdoc.core.fn.builders.apiresponse.Builder.responseBuilder;
|
||||
import static org.springdoc.core.fn.builders.content.Builder.contentBuilder;
|
||||
|
@ -274,10 +275,10 @@ public class AttachmentEndpoint implements CustomEndpoint {
|
|||
|
||||
public interface IUploadRequest {
|
||||
|
||||
@Schema(required = true, description = "Attachment file")
|
||||
@Schema(requiredMode = REQUIRED, description = "Attachment file")
|
||||
FilePart getFile();
|
||||
|
||||
@Schema(required = true, description = "Storage policy name")
|
||||
@Schema(requiredMode = REQUIRED, description = "Storage policy name")
|
||||
String getPolicyName();
|
||||
|
||||
@Schema(description = "The name of the group to which the attachment belongs")
|
||||
|
|
|
@ -429,7 +429,7 @@ public class UserEndpoint implements CustomEndpoint {
|
|||
}
|
||||
|
||||
record ChangePasswordRequest(
|
||||
@Schema(description = "New password.", required = true, minLength = 6)
|
||||
@Schema(description = "New password.", requiredMode = REQUIRED, minLength = 6)
|
||||
String password) {
|
||||
}
|
||||
|
||||
|
@ -522,8 +522,8 @@ public class UserEndpoint implements CustomEndpoint {
|
|||
.flatMapIterable(Function.identity());
|
||||
}
|
||||
|
||||
record UserPermission(@Schema(required = true) Set<Role> roles,
|
||||
@Schema(required = true) Set<String> uiPermissions) {
|
||||
record UserPermission(@Schema(requiredMode = REQUIRED) Set<Role> roles,
|
||||
@Schema(requiredMode = REQUIRED) Set<String> uiPermissions) {
|
||||
}
|
||||
|
||||
public class ListRequest extends IListRequest.QueryListRequest {
|
||||
|
|
|
@ -3,7 +3,6 @@ package run.halo.app.core.extension.reconciler;
|
|||
import static org.apache.commons.lang3.ObjectUtils.defaultIfNull;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Path;
|
||||
import java.time.Instant;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
|
@ -28,11 +27,10 @@ import run.halo.app.extension.controller.Reconciler.Request;
|
|||
import run.halo.app.infra.Condition;
|
||||
import run.halo.app.infra.ConditionStatus;
|
||||
import run.halo.app.infra.SystemVersionSupplier;
|
||||
import run.halo.app.infra.ThemeRootGetter;
|
||||
import run.halo.app.infra.exception.ThemeUninstallException;
|
||||
import run.halo.app.infra.properties.HaloProperties;
|
||||
import run.halo.app.infra.utils.JsonUtils;
|
||||
import run.halo.app.infra.utils.VersionUtils;
|
||||
import run.halo.app.theme.ThemePathPolicy;
|
||||
|
||||
/**
|
||||
* Reconciler for theme.
|
||||
|
@ -45,7 +43,8 @@ public class ThemeReconciler implements Reconciler<Request> {
|
|||
private static final String FINALIZER_NAME = "theme-protection";
|
||||
|
||||
private final ExtensionClient client;
|
||||
private final ThemePathPolicy themePathPolicy;
|
||||
|
||||
private final ThemeRootGetter themeRoot;
|
||||
private final SystemVersionSupplier systemVersionSupplier;
|
||||
|
||||
private final RetryTemplate retryTemplate = RetryTemplate.builder()
|
||||
|
@ -54,10 +53,10 @@ public class ThemeReconciler implements Reconciler<Request> {
|
|||
.retryOn(IllegalStateException.class)
|
||||
.build();
|
||||
|
||||
public ThemeReconciler(ExtensionClient client, HaloProperties haloProperties,
|
||||
public ThemeReconciler(ExtensionClient client, ThemeRootGetter themeRoot,
|
||||
SystemVersionSupplier systemVersionSupplier) {
|
||||
this.client = client;
|
||||
themePathPolicy = new ThemePathPolicy(haloProperties.getWorkDir());
|
||||
this.themeRoot = themeRoot;
|
||||
this.systemVersionSupplier = systemVersionSupplier;
|
||||
}
|
||||
|
||||
|
@ -90,7 +89,7 @@ public class ThemeReconciler implements Reconciler<Request> {
|
|||
final Theme.ThemeStatus oldStatus = JsonUtils.deepCopy(status);
|
||||
theme.setStatus(status);
|
||||
|
||||
Path themePath = themePathPolicy.generate(theme);
|
||||
var themePath = themeRoot.get().resolve(name);
|
||||
status.setLocation(themePath.toAbsolutePath().toString());
|
||||
|
||||
status.setPhase(Theme.ThemePhase.READY);
|
||||
|
@ -216,7 +215,7 @@ public class ThemeReconciler implements Reconciler<Request> {
|
|||
}
|
||||
|
||||
private void deleteThemeFiles(Theme theme) {
|
||||
Path themeDir = themePathPolicy.generate(theme);
|
||||
var themeDir = themeRoot.get().resolve(theme.getMetadata().getName());
|
||||
try {
|
||||
FileSystemUtils.deleteRecursively(themeDir);
|
||||
} catch (IOException e) {
|
||||
|
|
|
@ -3,14 +3,10 @@ package run.halo.app.core.extension.service;
|
|||
import static run.halo.app.extension.MetadataUtil.nullSafeLabels;
|
||||
|
||||
import com.fasterxml.jackson.core.type.TypeReference;
|
||||
import java.util.ArrayDeque;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.Comparator;
|
||||
import java.util.Deque;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.function.Predicate;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
|
@ -19,7 +15,6 @@ import org.apache.commons.lang3.StringUtils;
|
|||
import org.springframework.lang.NonNull;
|
||||
import org.springframework.stereotype.Service;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
import run.halo.app.core.extension.Role;
|
||||
import run.halo.app.core.extension.RoleBinding;
|
||||
import run.halo.app.core.extension.RoleBinding.RoleRef;
|
||||
|
@ -42,17 +37,6 @@ public class DefaultRoleService implements RoleService {
|
|||
this.extensionClient = extensionClient;
|
||||
}
|
||||
|
||||
@Override
|
||||
@NonNull
|
||||
public Role getRole(@NonNull String name) {
|
||||
return extensionClient.fetch(Role.class, name).blockOptional().orElseThrow();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Mono<Role> getMonoRole(String name) {
|
||||
return extensionClient.get(Role.class, name);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Flux<RoleRef> listRoleRefs(Subject subject) {
|
||||
return extensionClient.list(RoleBinding.class,
|
||||
|
@ -61,39 +45,6 @@ public class DefaultRoleService implements RoleService {
|
|||
.map(RoleBinding::getRoleRef);
|
||||
}
|
||||
|
||||
@Override
|
||||
@NonNull
|
||||
public List<Role> listDependencies(Set<String> names) {
|
||||
List<Role> result = new ArrayList<>();
|
||||
if (names == null) {
|
||||
return result;
|
||||
}
|
||||
Set<String> visited = new HashSet<>();
|
||||
Deque<String> queue = new ArrayDeque<>(names);
|
||||
while (!queue.isEmpty()) {
|
||||
String roleName = queue.poll();
|
||||
// detecting cycle in role dependencies
|
||||
if (visited.contains(roleName)) {
|
||||
log.warn("Detected a cycle in role dependencies: {},and skipped automatically",
|
||||
roleName);
|
||||
continue;
|
||||
}
|
||||
visited.add(roleName);
|
||||
extensionClient.fetch(Role.class, roleName)
|
||||
.blockOptional()
|
||||
.ifPresent(role -> {
|
||||
result.add(role);
|
||||
Map<String, String> annotations = role.getMetadata().getAnnotations();
|
||||
if (annotations != null) {
|
||||
String roleNameDependencies = annotations.get(Role.ROLE_DEPENDENCIES_ANNO);
|
||||
List<String> roleDependencies = stringToList(roleNameDependencies);
|
||||
queue.addAll(roleDependencies);
|
||||
}
|
||||
});
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Flux<Role> listDependenciesFlux(Set<String> names) {
|
||||
if (names == null) {
|
||||
|
|
|
@ -1,10 +1,7 @@
|
|||
package run.halo.app.core.extension.service;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import org.springframework.lang.NonNull;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
import run.halo.app.core.extension.Role;
|
||||
import run.halo.app.core.extension.RoleBinding.RoleRef;
|
||||
import run.halo.app.core.extension.RoleBinding.Subject;
|
||||
|
@ -15,16 +12,8 @@ import run.halo.app.core.extension.RoleBinding.Subject;
|
|||
*/
|
||||
public interface RoleService {
|
||||
|
||||
@NonNull
|
||||
@Deprecated
|
||||
Role getRole(String name);
|
||||
|
||||
Mono<Role> getMonoRole(String name);
|
||||
|
||||
Flux<RoleRef> listRoleRefs(Subject subject);
|
||||
|
||||
List<Role> listDependencies(Set<String> names);
|
||||
|
||||
Flux<Role> listDependenciesFlux(Set<String> names);
|
||||
|
||||
Flux<Role> list(Set<String> roleNames);
|
||||
|
|
|
@ -410,7 +410,7 @@ public class ThemeEndpoint implements CustomEndpoint {
|
|||
|
||||
public interface IUpgradeRequest {
|
||||
|
||||
@Schema(required = true, description = "Theme zip file.")
|
||||
@Schema(requiredMode = REQUIRED, description = "Theme zip file.")
|
||||
FilePart getFile();
|
||||
|
||||
}
|
||||
|
@ -501,7 +501,7 @@ public class ThemeEndpoint implements CustomEndpoint {
|
|||
|
||||
@Schema(name = "ThemeInstallRequest")
|
||||
public record InstallRequest(
|
||||
@Schema(required = true, description = "Theme zip file.") FilePart file) {
|
||||
@Schema(requiredMode = REQUIRED, description = "Theme zip file.") FilePart file) {
|
||||
}
|
||||
|
||||
public record InstallFromUriRequest(@Schema(requiredMode = REQUIRED) URI uri) {
|
||||
|
|
|
@ -102,6 +102,7 @@ public class ReactiveExtensionClientImpl implements ReactiveExtensionClient {
|
|||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public <E extends Extension> Mono<E> create(E extension) {
|
||||
return Mono.just(extension)
|
||||
.doOnNext(ext -> {
|
||||
|
@ -133,6 +134,7 @@ public class ReactiveExtensionClientImpl implements ReactiveExtensionClient {
|
|||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public <E extends Extension> Mono<E> update(E extension) {
|
||||
// Refactor the atomic reference if we have a better solution.
|
||||
final var statusChangeOnly = new AtomicBoolean(false);
|
||||
|
@ -181,6 +183,7 @@ public class ReactiveExtensionClientImpl implements ReactiveExtensionClient {
|
|||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("unchecked")
|
||||
public <E extends Extension> Mono<E> delete(E extension) {
|
||||
// set deletionTimestamp
|
||||
extension.getMetadata().setDeletionTimestamp(Instant.now());
|
||||
|
|
|
@ -1,169 +0,0 @@
|
|||
package run.halo.app.metrics;
|
||||
|
||||
import java.io.BufferedOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import java.nio.file.StandardOpenOption;
|
||||
import java.time.Instant;
|
||||
import java.util.concurrent.BlockingQueue;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.LinkedBlockingDeque;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.apache.commons.lang3.exception.ExceptionUtils;
|
||||
import org.springframework.beans.factory.DisposableBean;
|
||||
import org.springframework.beans.factory.InitializingBean;
|
||||
import reactor.core.Disposable;
|
||||
import run.halo.app.infra.properties.HaloProperties;
|
||||
import run.halo.app.infra.utils.FileUtils;
|
||||
|
||||
/**
|
||||
* Visit log writer.
|
||||
*
|
||||
* @author guqing
|
||||
* @since 2.0.0
|
||||
*/
|
||||
@Slf4j
|
||||
@Deprecated
|
||||
public class VisitLogWriter implements InitializingBean, DisposableBean {
|
||||
private static final String LOG_FILE_NAME = "visits.log";
|
||||
private static final String LOG_FILE_LOCATION = "logs";
|
||||
private final AsyncLogWriter asyncLogWriter;
|
||||
private volatile boolean interruptThread = false;
|
||||
private volatile boolean started = false;
|
||||
private final ExecutorService executorService;
|
||||
|
||||
private final Path logFilePath;
|
||||
|
||||
public VisitLogWriter(HaloProperties haloProperties) throws IOException {
|
||||
Path logsPath = haloProperties.getWorkDir()
|
||||
.resolve(LOG_FILE_LOCATION);
|
||||
if (!Files.exists(logsPath)) {
|
||||
Files.createDirectories(logsPath);
|
||||
}
|
||||
this.logFilePath = logsPath.resolve(LOG_FILE_NAME);
|
||||
this.asyncLogWriter = new AsyncLogWriter(logFilePath);
|
||||
this.executorService = Executors.newFixedThreadPool(1);
|
||||
}
|
||||
|
||||
public void log(String logMsg) {
|
||||
asyncLogWriter.put(logMsg);
|
||||
}
|
||||
|
||||
public Path getLogFilePath() {
|
||||
return logFilePath;
|
||||
}
|
||||
|
||||
void start() {
|
||||
if (started) {
|
||||
return;
|
||||
}
|
||||
log.debug("Starting write visit log...");
|
||||
this.started = true;
|
||||
executorService.submit(() -> {
|
||||
while (!interruptThread && !Thread.currentThread().isInterrupted()) {
|
||||
try {
|
||||
asyncLogWriter.writeLog();
|
||||
} catch (InterruptedException e) {
|
||||
Thread.currentThread().interrupt();
|
||||
log.warn("VisitLogWrite thread [{}] interrupted",
|
||||
Thread.currentThread().getName());
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public void afterPropertiesSet() throws Exception {
|
||||
start();
|
||||
}
|
||||
|
||||
public boolean isStarted() {
|
||||
return started;
|
||||
}
|
||||
|
||||
public long queuedSize() {
|
||||
return asyncLogWriter.logQueue.size();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void destroy() throws Exception {
|
||||
this.started = false;
|
||||
interruptThread = true;
|
||||
asyncLogWriter.dispose();
|
||||
executorService.shutdownNow();
|
||||
}
|
||||
|
||||
static class AsyncLogWriter implements Disposable {
|
||||
private static final int MAX_LOG_SIZE = 10000;
|
||||
private static final int BATCH_SIZE = 10;
|
||||
private final BufferedOutputStream writer;
|
||||
private final BlockingQueue<String> logQueue;
|
||||
private final AtomicInteger logBatch = new AtomicInteger(0);
|
||||
private volatile boolean disposed = false;
|
||||
|
||||
public AsyncLogWriter(Path logFilePath) {
|
||||
OutputStream outputStream;
|
||||
try {
|
||||
outputStream = Files.newOutputStream(logFilePath,
|
||||
StandardOpenOption.CREATE,
|
||||
StandardOpenOption.APPEND);
|
||||
} catch (IOException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
this.writer = new BufferedOutputStream(outputStream);
|
||||
this.logQueue = new LinkedBlockingDeque<>(MAX_LOG_SIZE);
|
||||
}
|
||||
|
||||
public void writeLog() throws InterruptedException {
|
||||
String logMessage = logQueue.take();
|
||||
if (StringUtils.isBlank(logMessage)) {
|
||||
return;
|
||||
}
|
||||
writeToDisk(logMessage);
|
||||
log.debug("Consumption visit log message: [{}]", logMessage);
|
||||
}
|
||||
|
||||
void writeToDisk(String logMsg) {
|
||||
String format = String.format("%s %s\n", Instant.now(), logMsg);
|
||||
try {
|
||||
writer.write(format.getBytes(), 0, format.length());
|
||||
int size = logBatch.incrementAndGet();
|
||||
if (size >= BATCH_SIZE) {
|
||||
writer.flush();
|
||||
logBatch.set(0);
|
||||
}
|
||||
} catch (IOException e) {
|
||||
log.warn("Record access log failure: ", ExceptionUtils.getRootCause(e));
|
||||
}
|
||||
}
|
||||
|
||||
public void put(String logMessage) {
|
||||
// add log message to queue tail
|
||||
logQueue.add(logMessage);
|
||||
log.debug("Production a log messages [{}]", logMessage);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void dispose() {
|
||||
this.disposed = true;
|
||||
if (writer != null) {
|
||||
try {
|
||||
writer.flush();
|
||||
} catch (IOException e) {
|
||||
// ignore this
|
||||
}
|
||||
FileUtils.closeQuietly(writer);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isDisposed() {
|
||||
return this.disposed;
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,5 +1,7 @@
|
|||
package run.halo.app.search.extension;
|
||||
|
||||
import static io.swagger.v3.oas.annotations.media.Schema.RequiredMode.REQUIRED;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Data;
|
||||
import lombok.EqualsAndHashCode;
|
||||
|
@ -15,7 +17,7 @@ import run.halo.app.extension.Ref;
|
|||
plural = "searchengines", singular = "searchengine")
|
||||
public class SearchEngine extends AbstractExtension {
|
||||
|
||||
@Schema(required = true)
|
||||
@Schema(requiredMode = REQUIRED)
|
||||
private SearchEngineSpec spec;
|
||||
|
||||
@Data
|
||||
|
@ -25,7 +27,7 @@ public class SearchEngine extends AbstractExtension {
|
|||
|
||||
private String website;
|
||||
|
||||
@Schema(required = true)
|
||||
@Schema(requiredMode = REQUIRED)
|
||||
private String displayName;
|
||||
|
||||
private String description;
|
||||
|
|
|
@ -1,9 +1,9 @@
|
|||
package run.halo.app.security;
|
||||
|
||||
import static org.springframework.security.web.server.csrf.CookieServerCsrfTokenRepository.withHttpOnlyFalse;
|
||||
import static org.springframework.security.web.server.util.matcher.ServerWebExchangeMatchers.pathMatchers;
|
||||
|
||||
import org.springframework.security.config.web.server.ServerHttpSecurity;
|
||||
import org.springframework.security.web.server.csrf.CookieServerCsrfTokenRepository;
|
||||
import org.springframework.security.web.server.csrf.CsrfWebFilter;
|
||||
import org.springframework.security.web.server.csrf.ServerCsrfTokenRequestAttributeHandler;
|
||||
import org.springframework.security.web.server.util.matcher.AndServerWebExchangeMatcher;
|
||||
|
@ -20,12 +20,12 @@ public class CsrfConfigurer implements SecurityConfigurer {
|
|||
CsrfWebFilter.DEFAULT_CSRF_MATCHER,
|
||||
new NegatedServerWebExchangeMatcher(pathMatchers("/api/**", "/apis/**")
|
||||
));
|
||||
|
||||
http.csrf().csrfTokenRepository(CookieServerCsrfTokenRepository.withHttpOnlyFalse())
|
||||
http.csrf(csrfSpec -> csrfSpec
|
||||
.csrfTokenRepository(withHttpOnlyFalse())
|
||||
// TODO Use XorServerCsrfTokenRequestAttributeHandler instead when console implements
|
||||
// the algorithm
|
||||
.csrfTokenRequestHandler(new ServerCsrfTokenRequestAttributeHandler())
|
||||
.requireCsrfProtectionMatcher(csrfMatcher);
|
||||
.requireCsrfProtectionMatcher(csrfMatcher));
|
||||
}
|
||||
|
||||
}
|
||||
|
|
|
@ -9,28 +9,5 @@ import reactor.core.publisher.Mono;
|
|||
*/
|
||||
public interface AuthorizationRuleResolver {
|
||||
|
||||
/**
|
||||
* rulesFor returns the list of rules that apply to a given user.
|
||||
* If an error is returned, the slice of PolicyRules may not be complete,
|
||||
* but it contains all retrievable rules.
|
||||
* This is done because policy rules are purely additive and policy determinations
|
||||
* can be made on the basis of those rules that are found.
|
||||
*
|
||||
* @param user authenticated user info
|
||||
*/
|
||||
@Deprecated(forRemoval = true, since = "2.0.0")
|
||||
PolicyRuleList rulesFor(UserDetails user);
|
||||
|
||||
/**
|
||||
* visitRulesFor invokes visitor() with each rule that applies to a given user
|
||||
* and each error encountered resolving those rules. Rule may be null if err is non-nil.
|
||||
* If visitor() returns false, visiting is short-circuited.
|
||||
*
|
||||
* @param user user info
|
||||
* @param visitor visitor
|
||||
*/
|
||||
@Deprecated(forRemoval = true, since = "2.0.0")
|
||||
void visitRulesFor(UserDetails user, RuleAccumulator visitor);
|
||||
|
||||
Mono<AuthorizingVisitor> visitRules(UserDetails user, RequestInfo requestInfo);
|
||||
}
|
||||
|
|
|
@ -1,27 +1,17 @@
|
|||
package run.halo.app.security.authorization;
|
||||
|
||||
import com.fasterxml.jackson.core.JsonProcessingException;
|
||||
import com.fasterxml.jackson.core.type.TypeReference;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.concurrent.atomic.AtomicBoolean;
|
||||
import lombok.Data;
|
||||
import lombok.extern.slf4j.Slf4j;
|
||||
import org.apache.commons.lang3.StringUtils;
|
||||
import org.springframework.security.core.userdetails.UserDetails;
|
||||
import org.springframework.util.Assert;
|
||||
import org.springframework.util.CollectionUtils;
|
||||
import reactor.core.publisher.Mono;
|
||||
import run.halo.app.core.extension.Role;
|
||||
import run.halo.app.core.extension.service.DefaultRoleBindingService;
|
||||
import run.halo.app.core.extension.service.RoleBindingService;
|
||||
import run.halo.app.core.extension.service.RoleService;
|
||||
import run.halo.app.extension.MetadataOperator;
|
||||
import run.halo.app.infra.AnonymousUserConst;
|
||||
import run.halo.app.infra.utils.JsonUtils;
|
||||
|
||||
/**
|
||||
* @author guqing
|
||||
|
@ -33,57 +23,11 @@ public class DefaultRuleResolver implements AuthorizationRuleResolver {
|
|||
private static final String AUTHENTICATED_ROLE = "authenticated";
|
||||
private RoleService roleService;
|
||||
|
||||
private RoleBindingService roleBindingService = new DefaultRoleBindingService();
|
||||
private RoleBindingService roleBindingService;
|
||||
|
||||
public DefaultRuleResolver(RoleService roleService) {
|
||||
this.roleService = roleService;
|
||||
}
|
||||
|
||||
@Override
|
||||
public PolicyRuleList rulesFor(UserDetails user) {
|
||||
PolicyRuleList policyRules = new PolicyRuleList();
|
||||
visitRulesFor(user, (source, rule, err) -> {
|
||||
if (rule != null) {
|
||||
policyRules.add(rule);
|
||||
}
|
||||
if (err != null) {
|
||||
policyRules.addError(err);
|
||||
}
|
||||
return true;
|
||||
});
|
||||
return policyRules;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void visitRulesFor(UserDetails user, RuleAccumulator visitor) {
|
||||
Set<String> roleNamesImmutable =
|
||||
roleBindingService.listBoundRoleNames(user.getAuthorities());
|
||||
Set<String> roleNames = new HashSet<>(roleNamesImmutable);
|
||||
if (!AnonymousUserConst.PRINCIPAL.equals(user.getUsername())) {
|
||||
roleNames.add(AUTHENTICATED_ROLE);
|
||||
roleNames.add(AnonymousUserConst.Role);
|
||||
}
|
||||
|
||||
List<Role.PolicyRule> rules = Collections.emptyList();
|
||||
for (String roleName : roleNames) {
|
||||
try {
|
||||
Role role = roleService.getRole(roleName);
|
||||
// fetch rules from role
|
||||
rules = fetchRules(role);
|
||||
} catch (Exception e) {
|
||||
if (visitor.visit(null, null, e)) {
|
||||
// if visitor returns true, we continue visiting
|
||||
continue;
|
||||
}
|
||||
}
|
||||
|
||||
String source = roleBindingDescriber(roleName, user.getUsername());
|
||||
for (Role.PolicyRule rule : rules) {
|
||||
if (!visitor.visit(source, rule, null)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
this.roleBindingService = new DefaultRoleBindingService();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -123,32 +67,6 @@ public class DefaultRuleResolver implements AuthorizationRuleResolver {
|
|||
.then(Mono.just(visitor));
|
||||
}
|
||||
|
||||
private List<Role.PolicyRule> fetchRules(Role role) {
|
||||
MetadataOperator metadata = role.getMetadata();
|
||||
if (metadata == null || metadata.getAnnotations() == null) {
|
||||
return role.getRules();
|
||||
}
|
||||
|
||||
// merge policy rules
|
||||
String roleDependencyRules = metadata.getAnnotations()
|
||||
.get(Role.ROLE_DEPENDENCY_RULES);
|
||||
List<Role.PolicyRule> rules = convertFrom(roleDependencyRules);
|
||||
rules.addAll(role.getRules());
|
||||
return rules;
|
||||
}
|
||||
|
||||
private List<Role.PolicyRule> convertFrom(String json) {
|
||||
if (StringUtils.isBlank(json)) {
|
||||
return new ArrayList<>();
|
||||
}
|
||||
try {
|
||||
return JsonUtils.DEFAULT_JSON_MAPPER.readValue(json, new TypeReference<>() {
|
||||
});
|
||||
} catch (JsonProcessingException e) {
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
}
|
||||
|
||||
String roleBindingDescriber(String roleName, String subject) {
|
||||
return String.format("Binding role [%s] to [%s]", roleName, subject);
|
||||
}
|
||||
|
|
|
@ -1,34 +0,0 @@
|
|||
package run.halo.app.theme;
|
||||
|
||||
import java.nio.file.Path;
|
||||
import org.springframework.util.Assert;
|
||||
import run.halo.app.core.extension.Theme;
|
||||
|
||||
/**
|
||||
* Policy for generating theme directory path.
|
||||
*
|
||||
* @author guqing
|
||||
* @since 2.0.0
|
||||
* @deprecated Use {@code run.halo.app.infra.ThemeRootGetter}
|
||||
*/
|
||||
@Deprecated(forRemoval = true)
|
||||
public class ThemePathPolicy {
|
||||
public static final String THEME_WORK_DIR = "themes";
|
||||
|
||||
private final Path workDir;
|
||||
|
||||
public ThemePathPolicy(Path workDir) {
|
||||
Assert.notNull(workDir, "The halo workDir must not be null.");
|
||||
this.workDir = workDir;
|
||||
}
|
||||
|
||||
public Path generate(Theme theme) {
|
||||
Assert.notNull(theme, "The theme must not be null.");
|
||||
String name = theme.getMetadata().getName();
|
||||
return themesDir().resolve(name);
|
||||
}
|
||||
|
||||
public Path themesDir() {
|
||||
return workDir.resolve(ThemePathPolicy.THEME_WORK_DIR);
|
||||
}
|
||||
}
|
|
@ -1,5 +1,7 @@
|
|||
package run.halo.app.theme.finders.vo;
|
||||
|
||||
import static io.swagger.v3.oas.annotations.media.Schema.RequiredMode.REQUIRED;
|
||||
|
||||
import io.swagger.v3.oas.annotations.media.Schema;
|
||||
import lombok.Builder;
|
||||
import lombok.Data;
|
||||
|
@ -21,16 +23,16 @@ import run.halo.app.extension.MetadataOperator;
|
|||
@EqualsAndHashCode
|
||||
public class ReplyVo implements ExtensionVoOperator {
|
||||
|
||||
@Schema(required = true)
|
||||
@Schema(requiredMode = REQUIRED)
|
||||
private MetadataOperator metadata;
|
||||
|
||||
@Schema(required = true)
|
||||
@Schema(requiredMode = REQUIRED)
|
||||
private Reply.ReplySpec spec;
|
||||
|
||||
@Schema(required = true)
|
||||
@Schema(requiredMode = REQUIRED)
|
||||
private OwnerInfo owner;
|
||||
|
||||
@Schema(required = true)
|
||||
@Schema(requiredMode = REQUIRED)
|
||||
private CommentStatsVo stats;
|
||||
|
||||
/**
|
||||
|
|
|
@ -5,7 +5,6 @@ import static org.junit.jupiter.api.Assertions.assertNotEquals;
|
|||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import static org.mockito.ArgumentMatchers.anySet;
|
||||
import static org.mockito.ArgumentMatchers.anyString;
|
||||
import static org.mockito.Mockito.when;
|
||||
import static org.springframework.security.test.web.reactive.server.SecurityMockServerConfigurers.csrf;
|
||||
|
||||
|
@ -25,7 +24,6 @@ import org.springframework.http.MediaType;
|
|||
import org.springframework.security.test.context.support.WithMockUser;
|
||||
import org.springframework.test.web.reactive.server.WebTestClient;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
import run.halo.app.core.extension.Role;
|
||||
import run.halo.app.core.extension.service.RoleService;
|
||||
import run.halo.app.extension.ExtensionClient;
|
||||
|
@ -60,7 +58,6 @@ class ExtensionConfigurationTest {
|
|||
role.setMetadata(new Metadata());
|
||||
role.getMetadata().setName("supper-role");
|
||||
role.setRules(List.of(rule));
|
||||
when(roleService.getMonoRole(anyString())).thenReturn(Mono.just(role));
|
||||
when(roleService.listDependenciesFlux(anySet())).thenReturn(Flux.just(role));
|
||||
// register scheme
|
||||
schemeManager.register(FakeExtension.class);
|
||||
|
|
|
@ -17,13 +17,11 @@ import org.springframework.http.MediaType;
|
|||
import org.springframework.security.test.context.support.WithMockUser;
|
||||
import org.springframework.test.web.reactive.server.WebTestClient;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
import run.halo.app.core.extension.Role;
|
||||
import run.halo.app.core.extension.content.Post;
|
||||
import run.halo.app.core.extension.service.RoleService;
|
||||
import run.halo.app.extension.Metadata;
|
||||
import run.halo.app.extension.MetadataOperator;
|
||||
import run.halo.app.extension.ReactiveExtensionClient;
|
||||
import run.halo.app.infra.utils.JsonUtils;
|
||||
|
||||
/**
|
||||
|
@ -44,9 +42,6 @@ public class PostIntegrationTests {
|
|||
@MockBean
|
||||
RoleService roleService;
|
||||
|
||||
@Autowired
|
||||
ReactiveExtensionClient client;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
var rule = new Role.PolicyRule.Builder()
|
||||
|
@ -58,7 +53,6 @@ public class PostIntegrationTests {
|
|||
role.setMetadata(new Metadata());
|
||||
role.getMetadata().setName("super-role");
|
||||
role.setRules(List.of(rule));
|
||||
when(roleService.getMonoRole("authenticated")).thenReturn(Mono.just(role));
|
||||
when(roleService.listDependenciesFlux(anySet())).thenReturn(Flux.just(role));
|
||||
webTestClient = webTestClient.mutateWith(csrf());
|
||||
}
|
||||
|
|
|
@ -81,17 +81,8 @@ class UserEndpointTest {
|
|||
@BeforeEach
|
||||
void setUp() {
|
||||
// disable authorization
|
||||
var rule = new Role.PolicyRule.Builder()
|
||||
.apiGroups("*")
|
||||
.resources("*")
|
||||
.verbs("*")
|
||||
.build();
|
||||
var role = new Role();
|
||||
role.setRules(List.of(rule));
|
||||
when(roleService.getMonoRole("authenticated")).thenReturn(Mono.just(role));
|
||||
webClient = WebTestClient.bindToRouterFunction(endpoint.endpoint())
|
||||
.build();
|
||||
webClient = webClient.mutateWith(csrf());
|
||||
webClient = WebTestClient.bindToRouterFunction(endpoint.endpoint()).build()
|
||||
.mutateWith(csrf());
|
||||
}
|
||||
|
||||
@Nested
|
||||
|
|
|
@ -19,11 +19,12 @@ import java.util.List;
|
|||
import java.util.Map;
|
||||
import java.util.Optional;
|
||||
import org.json.JSONException;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.junit.jupiter.api.io.TempDir;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
import org.mockito.stubbing.Answer;
|
||||
|
@ -40,9 +41,8 @@ import run.halo.app.extension.Metadata;
|
|||
import run.halo.app.extension.MetadataOperator;
|
||||
import run.halo.app.extension.controller.Reconciler;
|
||||
import run.halo.app.infra.SystemVersionSupplier;
|
||||
import run.halo.app.infra.properties.HaloProperties;
|
||||
import run.halo.app.infra.ThemeRootGetter;
|
||||
import run.halo.app.infra.utils.JsonUtils;
|
||||
import run.halo.app.theme.ThemePathPolicy;
|
||||
|
||||
/**
|
||||
* Tests for {@link ThemeReconciler}.
|
||||
|
@ -57,37 +57,31 @@ class ThemeReconcilerTest {
|
|||
private ExtensionClient extensionClient;
|
||||
|
||||
@Mock
|
||||
private HaloProperties haloProperties;
|
||||
private SystemVersionSupplier systemVersionSupplier;
|
||||
|
||||
@Mock
|
||||
private SystemVersionSupplier systemVersionSupplier;
|
||||
ThemeRootGetter themeRoot;
|
||||
|
||||
@Mock
|
||||
private File defaultTheme;
|
||||
|
||||
@InjectMocks
|
||||
ThemeReconciler themeReconciler;
|
||||
|
||||
@TempDir
|
||||
private Path tempDirectory;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() throws IOException {
|
||||
tempDirectory = Files.createTempDirectory("halo-theme-");
|
||||
defaultTheme = ResourceUtils.getFile("classpath:themes/default");
|
||||
lenient().when(systemVersionSupplier.get()).thenReturn(Version.valueOf("0.0.0"));
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
void tearDown() throws IOException {
|
||||
FileSystemUtils.deleteRecursively(tempDirectory);
|
||||
}
|
||||
|
||||
@Test
|
||||
void reconcileDelete() throws IOException {
|
||||
Path testWorkDir = tempDirectory.resolve("reconcile-delete");
|
||||
Files.createDirectory(testWorkDir);
|
||||
when(haloProperties.getWorkDir()).thenReturn(testWorkDir);
|
||||
|
||||
final ThemeReconciler themeReconciler =
|
||||
new ThemeReconciler(extensionClient, haloProperties, systemVersionSupplier);
|
||||
final ThemePathPolicy themePathPolicy = new ThemePathPolicy(testWorkDir);
|
||||
when(themeRoot.get()).thenReturn(testWorkDir);
|
||||
|
||||
Theme theme = new Theme();
|
||||
Metadata metadata = new Metadata();
|
||||
|
@ -100,7 +94,7 @@ class ThemeReconcilerTest {
|
|||
themeSpec.setSettingName("theme-test-setting");
|
||||
theme.setSpec(themeSpec);
|
||||
|
||||
Path defaultThemePath = themePathPolicy.generate(theme);
|
||||
Path defaultThemePath = testWorkDir.resolve("theme-test");
|
||||
|
||||
// copy to temp directory
|
||||
FileSystemUtils.copyRecursively(defaultTheme.toPath(), defaultThemePath);
|
||||
|
@ -130,10 +124,10 @@ class ThemeReconcilerTest {
|
|||
final MetadataOperator metadata = theme.getMetadata();
|
||||
|
||||
Path testWorkDir = tempDirectory.resolve("reconcile-delete");
|
||||
when(haloProperties.getWorkDir()).thenReturn(testWorkDir);
|
||||
when(themeRoot.get()).thenReturn(testWorkDir);
|
||||
|
||||
final ThemeReconciler themeReconciler =
|
||||
new ThemeReconciler(extensionClient, haloProperties, systemVersionSupplier);
|
||||
new ThemeReconciler(extensionClient, themeRoot, systemVersionSupplier);
|
||||
|
||||
final int[] retryFlags = {0, 0};
|
||||
when(extensionClient.fetch(eq(Setting.class), eq("theme-test-setting")))
|
||||
|
@ -169,10 +163,10 @@ class ThemeReconcilerTest {
|
|||
Theme theme = fakeTheme();
|
||||
|
||||
Path testWorkDir = tempDirectory.resolve("reconcile-delete");
|
||||
when(haloProperties.getWorkDir()).thenReturn(testWorkDir);
|
||||
when(themeRoot.get()).thenReturn(testWorkDir);
|
||||
|
||||
final ThemeReconciler themeReconciler =
|
||||
new ThemeReconciler(extensionClient, haloProperties, systemVersionSupplier);
|
||||
new ThemeReconciler(extensionClient, themeRoot, systemVersionSupplier);
|
||||
|
||||
final int[] retryFlags = {0};
|
||||
when(extensionClient.fetch(eq(Setting.class), eq("theme-test-setting")))
|
||||
|
@ -198,10 +192,10 @@ class ThemeReconcilerTest {
|
|||
void reconcileStatus() {
|
||||
when(systemVersionSupplier.get()).thenReturn(Version.valueOf("2.3.0"));
|
||||
Path testWorkDir = tempDirectory.resolve("reconcile-delete");
|
||||
when(haloProperties.getWorkDir()).thenReturn(testWorkDir);
|
||||
when(themeRoot.get()).thenReturn(testWorkDir);
|
||||
|
||||
final ThemeReconciler themeReconciler =
|
||||
new ThemeReconciler(extensionClient, haloProperties, systemVersionSupplier);
|
||||
new ThemeReconciler(extensionClient, themeRoot, systemVersionSupplier);
|
||||
Theme theme = fakeTheme();
|
||||
theme.setStatus(null);
|
||||
theme.getSpec().setRequires(">2.3.0");
|
||||
|
@ -246,10 +240,7 @@ class ThemeReconcilerTest {
|
|||
void themeSettingDefaultValue() throws IOException, JSONException {
|
||||
Path testWorkDir = tempDirectory.resolve("reconcile-setting-value");
|
||||
Files.createDirectory(testWorkDir);
|
||||
when(haloProperties.getWorkDir()).thenReturn(testWorkDir);
|
||||
|
||||
final ThemeReconciler themeReconciler =
|
||||
new ThemeReconciler(extensionClient, haloProperties, systemVersionSupplier);
|
||||
when(themeRoot.get()).thenReturn(testWorkDir);
|
||||
|
||||
Theme theme = new Theme();
|
||||
Metadata metadata = new Metadata();
|
||||
|
|
|
@ -1,9 +1,7 @@
|
|||
package run.halo.app.core.extension.service;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.anyString;
|
||||
import static org.mockito.ArgumentMatchers.same;
|
||||
import static org.mockito.Mockito.eq;
|
||||
import static org.mockito.Mockito.lenient;
|
||||
import static org.mockito.Mockito.times;
|
||||
|
@ -17,10 +15,10 @@ import java.util.Set;
|
|||
import java.util.function.Predicate;
|
||||
import java.util.stream.Stream;
|
||||
import org.assertj.core.api.AssertionsForInterfaceTypes;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Nested;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
import reactor.core.publisher.Flux;
|
||||
|
@ -28,7 +26,6 @@ import reactor.core.publisher.Mono;
|
|||
import reactor.test.StepVerifier;
|
||||
import run.halo.app.core.extension.Role;
|
||||
import run.halo.app.core.extension.RoleBinding;
|
||||
import run.halo.app.core.extension.TestRole;
|
||||
import run.halo.app.extension.Metadata;
|
||||
import run.halo.app.extension.ReactiveExtensionClient;
|
||||
import run.halo.app.infra.utils.JsonUtils;
|
||||
|
@ -44,56 +41,9 @@ class DefaultRoleServiceTest {
|
|||
@Mock
|
||||
private ReactiveExtensionClient extensionClient;
|
||||
|
||||
@InjectMocks
|
||||
private DefaultRoleService roleService;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
roleService = new DefaultRoleService(extensionClient);
|
||||
}
|
||||
|
||||
@Test
|
||||
void listDependencies() {
|
||||
Role roleManage = TestRole.getRoleManage();
|
||||
Map<String, String> manageAnnotations = new HashMap<>();
|
||||
manageAnnotations.put(Role.ROLE_DEPENDENCIES_ANNO, "[\"role-template-apple-view\"]");
|
||||
roleManage.getMetadata().setAnnotations(manageAnnotations);
|
||||
|
||||
Role roleView = TestRole.getRoleView();
|
||||
Map<String, String> viewAnnotations = new HashMap<>();
|
||||
viewAnnotations.put(Role.ROLE_DEPENDENCIES_ANNO, "[\"role-template-apple-other\"]");
|
||||
roleView.getMetadata().setAnnotations(viewAnnotations);
|
||||
|
||||
Role roleOther = TestRole.getRoleOther();
|
||||
|
||||
when(extensionClient.fetch(same(Role.class), eq("role-template-apple-manage")))
|
||||
.thenReturn(Mono.just(roleManage));
|
||||
when(extensionClient.fetch(same(Role.class), eq("role-template-apple-view")))
|
||||
.thenReturn(Mono.just(roleView));
|
||||
when(extensionClient.fetch(same(Role.class), eq("role-template-apple-other")))
|
||||
.thenReturn(Mono.just(roleOther));
|
||||
|
||||
// list without cycle
|
||||
List<Role> roles = roleService.listDependencies(Set.of("role-template-apple-manage"));
|
||||
|
||||
verify(extensionClient, times(1)).fetch(same(Role.class), eq("role-template-apple-manage"));
|
||||
verify(extensionClient, times(1)).fetch(same(Role.class), eq("role-template-apple-view"));
|
||||
verify(extensionClient, times(1)).fetch(same(Role.class), eq("role-template-apple-other"));
|
||||
|
||||
assertThat(roles).hasSize(3);
|
||||
assertThat(roles).containsExactly(roleManage, roleView, roleOther);
|
||||
|
||||
// list dependencies with a cycle relation
|
||||
Map<String, String> anotherAnnotations = new HashMap<>();
|
||||
anotherAnnotations.put(Role.ROLE_DEPENDENCIES_ANNO, "[\"role-template-apple-view\"]");
|
||||
roleOther.getMetadata().setAnnotations(anotherAnnotations);
|
||||
when(extensionClient.fetch(same(Role.class), eq("role-template-apple-other")))
|
||||
.thenReturn(Mono.just(roleOther));
|
||||
// correct behavior is to ignore the cycle relation
|
||||
List<Role> rolesFromCycle =
|
||||
roleService.listDependencies(Set.of("role-template-apple-manage"));
|
||||
assertThat(rolesFromCycle).hasSize(3);
|
||||
}
|
||||
|
||||
@Nested
|
||||
class ListDependenciesTest {
|
||||
@Test
|
||||
|
|
|
@ -1,56 +0,0 @@
|
|||
package run.halo.app.metrics;
|
||||
|
||||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.mockito.Mockito.when;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.Path;
|
||||
import org.junit.jupiter.api.AfterEach;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Disabled;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
import org.springframework.util.FileSystemUtils;
|
||||
import run.halo.app.infra.properties.HaloProperties;
|
||||
|
||||
/**
|
||||
* Tests for {@link VisitLogWriter}.
|
||||
*
|
||||
* @author guqing
|
||||
* @since 2.0.0
|
||||
*/
|
||||
@Disabled
|
||||
@Deprecated
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class VisitLogWriterTest {
|
||||
|
||||
@Mock
|
||||
private HaloProperties haloProperties;
|
||||
|
||||
private VisitLogWriter visitLogWriter;
|
||||
|
||||
private Path workDir;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() throws IOException {
|
||||
workDir = Files.createTempDirectory("halo-visitlog");
|
||||
when(haloProperties.getWorkDir()).thenReturn(workDir);
|
||||
visitLogWriter = new VisitLogWriter(haloProperties);
|
||||
}
|
||||
|
||||
@AfterEach
|
||||
void tearDown() throws Exception {
|
||||
visitLogWriter.destroy();
|
||||
FileSystemUtils.deleteRecursively(workDir);
|
||||
}
|
||||
|
||||
@Test
|
||||
void start() {
|
||||
assertThat(visitLogWriter.isStarted()).isFalse();
|
||||
visitLogWriter.start();
|
||||
assertThat(visitLogWriter.isStarted()).isTrue();
|
||||
}
|
||||
}
|
|
@ -7,7 +7,6 @@ import static org.junit.jupiter.api.Assertions.assertNull;
|
|||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.Mockito.doNothing;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.never;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
|
@ -175,9 +174,8 @@ class BackupReconcilerTest {
|
|||
backup.getSpec().setFormat("zip");
|
||||
when(client.fetch(Backup.class, name)).thenReturn(Optional.of(backup));
|
||||
doNothing().when(client).update(backup);
|
||||
Mono<Void> mono = mock(Mono.class);
|
||||
when(mono.block()).thenThrow(Exceptions.propagate(new InterruptedException()));
|
||||
when(migrationService.backup(backup)).thenReturn(mono);
|
||||
when(migrationService.backup(backup)).thenReturn(
|
||||
Mono.error(Exceptions.propagate(new InterruptedException())));
|
||||
|
||||
var result = reconciler.reconcile(new Reconciler.Request(name));
|
||||
|
||||
|
@ -196,7 +194,6 @@ class BackupReconcilerTest {
|
|||
verify(client, times(3)).fetch(Backup.class, name);
|
||||
verify(client, times(3)).update(backup);
|
||||
verify(migrationService).backup(backup);
|
||||
verify(mono).block();
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -206,9 +203,8 @@ class BackupReconcilerTest {
|
|||
backup.getSpec().setFormat("zip");
|
||||
when(client.fetch(Backup.class, name)).thenReturn(Optional.of(backup));
|
||||
doNothing().when(client).update(backup);
|
||||
Mono<Void> mono = mock(Mono.class);
|
||||
when(mono.block()).thenThrow(Exceptions.propagate(new IOException("File not found")));
|
||||
when(migrationService.backup(backup)).thenReturn(mono);
|
||||
when(migrationService.backup(backup))
|
||||
.thenReturn(Mono.error(Exceptions.propagate(new IOException("File not found"))));
|
||||
|
||||
var result = reconciler.reconcile(new Reconciler.Request(name));
|
||||
|
||||
|
@ -227,7 +223,6 @@ class BackupReconcilerTest {
|
|||
verify(client, times(3)).fetch(Backup.class, name);
|
||||
verify(client, times(3)).update(backup);
|
||||
verify(migrationService).backup(backup);
|
||||
verify(mono).block();
|
||||
}
|
||||
|
||||
@Test
|
||||
|
|
|
@ -1,23 +0,0 @@
|
|||
package run.halo.app.security;
|
||||
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.test.web.reactive.server.WebTestClient;
|
||||
import reactor.core.publisher.Mono;
|
||||
import run.halo.app.security.authentication.jwt.LoginAuthenticationConverter;
|
||||
|
||||
public final class LoginUtils {
|
||||
|
||||
public static Mono<String> login(WebTestClient webClient, String username, String password) {
|
||||
var request = new LoginAuthenticationConverter.UsernamePasswordRequest();
|
||||
request.setUsername(username);
|
||||
request.setPassword(password);
|
||||
return webClient.post().uri("/api/auth/token")
|
||||
.header(HttpHeaders.ACCEPT, MediaType.APPLICATION_JSON_VALUE).bodyValue(request)
|
||||
.exchange().expectStatus().isOk().expectHeader().contentType(MediaType.APPLICATION_JSON)
|
||||
.returnResult(ResponseMap.class)
|
||||
.getResponseBody()
|
||||
.single()
|
||||
.map(responseMap -> responseMap.get("token").toString());
|
||||
}
|
||||
}
|
|
@ -1,86 +0,0 @@
|
|||
package run.halo.app.security.authentication.jwt;
|
||||
|
||||
import static org.hamcrest.Matchers.containsString;
|
||||
import static org.mockito.ArgumentMatchers.anyString;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.lenient;
|
||||
import static org.mockito.Mockito.when;
|
||||
import static org.springframework.security.test.web.reactive.server.SecurityMockServerConfigurers.csrf;
|
||||
|
||||
import java.util.List;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Disabled;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.autoconfigure.web.reactive.AutoConfigureWebTestClient;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.boot.test.mock.mockito.MockBean;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.security.core.userdetails.ReactiveUserDetailsService;
|
||||
import org.springframework.security.core.userdetails.User;
|
||||
import org.springframework.test.web.reactive.server.WebTestClient;
|
||||
import reactor.core.publisher.Mono;
|
||||
import run.halo.app.core.extension.Role;
|
||||
import run.halo.app.core.extension.service.RoleService;
|
||||
import run.halo.app.extension.Metadata;
|
||||
import run.halo.app.infra.AnonymousUserConst;
|
||||
import run.halo.app.security.LoginUtils;
|
||||
|
||||
@Disabled
|
||||
@SpringBootTest
|
||||
@AutoConfigureWebTestClient
|
||||
class JwtAuthenticationTest {
|
||||
|
||||
@Autowired
|
||||
WebTestClient webClient;
|
||||
|
||||
@MockBean
|
||||
ReactiveUserDetailsService userDetailsService;
|
||||
|
||||
@MockBean
|
||||
RoleService roleService;
|
||||
|
||||
@BeforeEach
|
||||
void setUp() {
|
||||
lenient().when(roleService.getMonoRole(eq(AnonymousUserConst.Role)))
|
||||
.thenReturn(Mono.empty());
|
||||
webClient = webClient.mutateWith(csrf());
|
||||
}
|
||||
|
||||
@Test
|
||||
void accessProtectedApiWithoutToken() {
|
||||
webClient.get().uri("/api/v1/test/hello").exchange().expectStatus().isUnauthorized();
|
||||
}
|
||||
|
||||
@Test
|
||||
void accessProtectedApiUsingBearerToken() {
|
||||
when(userDetailsService.findByUsername(anyString())).thenReturn(Mono.just(
|
||||
User.withDefaultPasswordEncoder()
|
||||
.username("username")
|
||||
.password("password")
|
||||
.roles("USER")
|
||||
.build()
|
||||
));
|
||||
|
||||
var role = new Role();
|
||||
var metadata = new Metadata();
|
||||
metadata.setName("USER");
|
||||
role.setMetadata(metadata);
|
||||
role.setRules(List.of(new Role.PolicyRule.Builder()
|
||||
.apiGroups("")
|
||||
.resources("test")
|
||||
.resourceNames("hello")
|
||||
.build()));
|
||||
when(roleService.getMonoRole("authenticated")).thenReturn(Mono.empty());
|
||||
when(roleService.getMonoRole("USER")).thenReturn(Mono.just(role));
|
||||
|
||||
final var token = LoginUtils.login(webClient, "username", "password").block();
|
||||
webClient.get().uri("/api/v1/test/hello")
|
||||
.header(HttpHeaders.AUTHORIZATION, "Bearer " + token)
|
||||
.exchange()
|
||||
.expectStatus().isForbidden()
|
||||
.expectHeader().value(HttpHeaders.WWW_AUTHENTICATE,
|
||||
containsString("insufficient_scope"));
|
||||
}
|
||||
|
||||
}
|
|
@ -1,110 +0,0 @@
|
|||
package run.halo.app.security.authentication.jwt;
|
||||
|
||||
import static org.hamcrest.Matchers.equalTo;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
import static org.mockito.Mockito.when;
|
||||
import static org.springframework.security.test.web.reactive.server.SecurityMockServerConfigurers.csrf;
|
||||
|
||||
import com.nimbusds.jwt.JWTClaimNames;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Disabled;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.autoconfigure.web.reactive.AutoConfigureWebTestClient;
|
||||
import org.springframework.boot.test.context.SpringBootTest;
|
||||
import org.springframework.boot.test.mock.mockito.MockBean;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.security.core.userdetails.ReactiveUserDetailsService;
|
||||
import org.springframework.security.core.userdetails.User;
|
||||
import org.springframework.security.crypto.password.PasswordEncoder;
|
||||
import org.springframework.security.oauth2.jwt.ReactiveJwtDecoder;
|
||||
import org.springframework.test.web.reactive.server.WebTestClient;
|
||||
import reactor.core.publisher.Mono;
|
||||
|
||||
@Disabled
|
||||
@SpringBootTest
|
||||
@AutoConfigureWebTestClient
|
||||
class LoginTest {
|
||||
|
||||
@Autowired
|
||||
WebTestClient webClient;
|
||||
|
||||
@MockBean
|
||||
ReactiveUserDetailsService userDetailsService;
|
||||
|
||||
@BeforeEach
|
||||
void setUp(@Autowired PasswordEncoder passwordEncoder) {
|
||||
when(userDetailsService.findByUsername("user")).thenReturn(Mono.just(
|
||||
User.builder()
|
||||
.passwordEncoder(passwordEncoder::encode)
|
||||
.username("user")
|
||||
.password("password")
|
||||
.roles("USER")
|
||||
.build()
|
||||
));
|
||||
|
||||
webClient = webClient.mutateWith(csrf());
|
||||
}
|
||||
|
||||
@Test
|
||||
void logintWithoutLoginRequest() {
|
||||
webClient.post().uri("/api/auth/token").exchange().expectStatus().isUnauthorized();
|
||||
}
|
||||
|
||||
@Test
|
||||
void loginWithoutPostMethod() {
|
||||
webClient.get().uri("/api/auth/token").exchange().expectStatus().isUnauthorized();
|
||||
webClient.put().uri("/api/auth/token").exchange().expectStatus().isUnauthorized();
|
||||
webClient.delete().uri("/api/auth/token").exchange().expectStatus().isUnauthorized();
|
||||
webClient.patch().uri("/api/auth/token").exchange().expectStatus().isUnauthorized();
|
||||
}
|
||||
|
||||
@Test
|
||||
void loginWithoutApplicationJsonAcceptHeader() {
|
||||
var request = new LoginAuthenticationConverter.UsernamePasswordRequest();
|
||||
request.setUsername("user");
|
||||
request.setPassword("invalid_password");
|
||||
webClient.post().uri("/api/auth/token")
|
||||
.header(HttpHeaders.ACCEPT, MediaType.APPLICATION_XML_VALUE).bodyValue(request)
|
||||
.exchange().expectStatus().isUnauthorized();
|
||||
}
|
||||
|
||||
@Test
|
||||
void loginWithInvalidCredential() {
|
||||
when(userDetailsService.findByUsername("user")).thenReturn(Mono.empty());
|
||||
var request = new LoginAuthenticationConverter.UsernamePasswordRequest();
|
||||
request.setUsername("user");
|
||||
request.setPassword("invalid_password");
|
||||
webClient.post().uri("/api/auth/token")
|
||||
.header(HttpHeaders.ACCEPT, MediaType.APPLICATION_JSON_VALUE).bodyValue(request)
|
||||
.exchange().expectStatus().isBadRequest().expectHeader()
|
||||
.contentType(MediaType.APPLICATION_JSON).expectBody()
|
||||
.jsonPath("$.error").value(equalTo("Invalid Credentials"));
|
||||
}
|
||||
|
||||
@Test
|
||||
void loginWithValidCredential(@Autowired ReactiveJwtDecoder jwtDecoder,
|
||||
@Autowired PasswordEncoder passwordEncoder) {
|
||||
when(userDetailsService.findByUsername("user")).thenReturn(Mono.just(
|
||||
User.builder()
|
||||
.passwordEncoder(passwordEncoder::encode)
|
||||
.username("user")
|
||||
.password("password")
|
||||
.roles("USER")
|
||||
.build()
|
||||
));
|
||||
var request = new LoginAuthenticationConverter.UsernamePasswordRequest();
|
||||
request.setUsername("user");
|
||||
request.setPassword("password");
|
||||
webClient.post().uri("/api/auth/token")
|
||||
.header(HttpHeaders.ACCEPT, MediaType.APPLICATION_JSON_VALUE).bodyValue(request)
|
||||
.exchange().expectStatus().isOk().expectHeader().contentType(MediaType.APPLICATION_JSON)
|
||||
.expectBody().jsonPath("$.token").value(token -> {
|
||||
var jwt = jwtDecoder.decode(token).block();
|
||||
assertNotNull(jwt);
|
||||
assertEquals("user", jwt.getClaim(JWTClaimNames.SUBJECT));
|
||||
}, String.class);
|
||||
}
|
||||
}
|
|
@ -1,6 +1,6 @@
|
|||
package run.halo.app.security.authorization;
|
||||
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.anySet;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
|
@ -10,12 +10,9 @@ import static org.springframework.web.reactive.function.server.RequestPredicates
|
|||
import static org.springframework.web.reactive.function.server.RequestPredicates.PUT;
|
||||
import static org.springframework.web.reactive.function.server.RequestPredicates.accept;
|
||||
import static org.springframework.web.reactive.function.server.RouterFunctions.route;
|
||||
import static run.halo.app.extension.GroupVersionKind.fromExtension;
|
||||
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import org.junit.jupiter.api.BeforeEach;
|
||||
import org.junit.jupiter.api.Disabled;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.beans.factory.annotation.Autowired;
|
||||
import org.springframework.boot.test.autoconfigure.web.reactive.AutoConfigureWebTestClient;
|
||||
|
@ -24,26 +21,23 @@ import org.springframework.boot.test.context.TestConfiguration;
|
|||
import org.springframework.boot.test.mock.mockito.MockBean;
|
||||
import org.springframework.context.annotation.Bean;
|
||||
import org.springframework.context.annotation.Import;
|
||||
import org.springframework.http.HttpHeaders;
|
||||
import org.springframework.http.MediaType;
|
||||
import org.springframework.lang.NonNull;
|
||||
import org.springframework.security.core.userdetails.ReactiveUserDetailsPasswordService;
|
||||
import org.springframework.security.core.userdetails.ReactiveUserDetailsService;
|
||||
import org.springframework.security.core.userdetails.User;
|
||||
import org.springframework.security.test.context.support.WithMockUser;
|
||||
import org.springframework.test.web.reactive.server.WebTestClient;
|
||||
import org.springframework.web.reactive.function.server.RouterFunction;
|
||||
import org.springframework.web.reactive.function.server.ServerRequest;
|
||||
import org.springframework.web.reactive.function.server.ServerResponse;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.core.publisher.Mono;
|
||||
import run.halo.app.core.extension.Role;
|
||||
import run.halo.app.core.extension.Role.PolicyRule;
|
||||
import run.halo.app.core.extension.service.RoleService;
|
||||
import run.halo.app.extension.Metadata;
|
||||
import run.halo.app.extension.exception.ExtensionNotFoundException;
|
||||
import run.halo.app.infra.AnonymousUserConst;
|
||||
import run.halo.app.security.LoginUtils;
|
||||
|
||||
@Disabled
|
||||
@SpringBootTest
|
||||
@AutoConfigureWebTestClient
|
||||
@Import(AuthorizationTest.TestConfig.class)
|
||||
|
@ -55,6 +49,9 @@ class AuthorizationTest {
|
|||
@MockBean
|
||||
ReactiveUserDetailsService userDetailsService;
|
||||
|
||||
@MockBean
|
||||
ReactiveUserDetailsPasswordService userDetailsPasswordService;
|
||||
|
||||
@MockBean
|
||||
RoleService roleService;
|
||||
|
||||
|
@ -63,64 +60,16 @@ class AuthorizationTest {
|
|||
webClient = webClient.mutateWith(csrf());
|
||||
}
|
||||
|
||||
@Test
|
||||
void accessProtectedApiWithoutSufficientRole() {
|
||||
when(userDetailsService.findByUsername(eq("user"))).thenReturn(
|
||||
Mono.just(User.withDefaultPasswordEncoder().username("user").password("password")
|
||||
.roles("invalid-role").build()));
|
||||
when(roleService.getMonoRole(any())).thenReturn(Mono.empty());
|
||||
var token = LoginUtils.login(webClient, "user", "password").block();
|
||||
webClient.get().uri("/apis/fake.halo.run/v1/posts")
|
||||
.header(HttpHeaders.AUTHORIZATION, "Bearer " + token).exchange().expectStatus()
|
||||
.isForbidden();
|
||||
|
||||
verify(roleService, times(1)).getMonoRole("authenticated");
|
||||
verify(roleService, times(1)).getMonoRole("invalid-role");
|
||||
}
|
||||
|
||||
@Test
|
||||
void accessProtectedApiWithSufficientRole() {
|
||||
when(userDetailsService.findByUsername(eq("user"))).thenReturn(Mono.just(
|
||||
User.withDefaultPasswordEncoder().username("user").password("password")
|
||||
.roles("post.read").build()));
|
||||
when(roleService.getMonoRole(eq(AnonymousUserConst.Role)))
|
||||
.thenReturn(Mono.empty());
|
||||
|
||||
var role = new Role();
|
||||
role.setRules(List.of(
|
||||
new PolicyRule.Builder().apiGroups("fake.halo.run").verbs("list").resources("posts")
|
||||
.build()));
|
||||
|
||||
when(roleService.getMonoRole("post.read")).thenReturn(Mono.just(role));
|
||||
when(roleService.getMonoRole("authenticated")).thenReturn(
|
||||
Mono.error(
|
||||
() -> new ExtensionNotFoundException(fromExtension(Role.class), "authenticated")));
|
||||
|
||||
var token = LoginUtils.login(webClient, "user", "password").block();
|
||||
webClient.get().uri("/apis/fake.halo.run/v1/posts")
|
||||
.header(HttpHeaders.AUTHORIZATION, "Bearer " + token)
|
||||
.exchange()
|
||||
.expectStatus().isOk()
|
||||
.expectBody(String.class).isEqualTo("returned posts");
|
||||
|
||||
webClient.put().uri("/apis/fake.halo.run/v1/posts/hello-halo")
|
||||
.header(HttpHeaders.AUTHORIZATION, "Bearer " + token).exchange()
|
||||
.expectStatus().isForbidden();
|
||||
|
||||
verify(roleService, times(2)).getMonoRole("authenticated");
|
||||
verify(roleService, times(2)).getMonoRole("post.read");
|
||||
}
|
||||
|
||||
@Test
|
||||
void anonymousUserAccessProtectedApi() {
|
||||
when(userDetailsService.findByUsername(eq(AnonymousUserConst.PRINCIPAL)))
|
||||
.thenReturn(Mono.empty());
|
||||
when(roleService.getMonoRole(AnonymousUserConst.Role))
|
||||
.thenReturn(Mono.empty());
|
||||
when(roleService.listDependenciesFlux(anySet())).thenReturn(Flux.empty());
|
||||
|
||||
webClient.get().uri("/apis/fake.halo.run/v1/posts").exchange().expectStatus()
|
||||
.isUnauthorized();
|
||||
|
||||
verify(roleService, times(1)).getMonoRole(AnonymousUserConst.Role);
|
||||
verify(roleService).listDependenciesFlux(anySet());
|
||||
}
|
||||
|
||||
@Test
|
||||
|
@ -137,25 +86,23 @@ class AuthorizationTest {
|
|||
.resources("posts")
|
||||
.build();
|
||||
role.getRules().add(policyRule);
|
||||
when(roleService.getMonoRole(AnonymousUserConst.Role))
|
||||
.thenReturn(Mono.just(role));
|
||||
when(roleService.listDependenciesFlux(anySet())).thenReturn(Flux.just(role));
|
||||
webClient.get().uri("/apis/fake.halo.run/v1/posts").exchange().expectStatus()
|
||||
.isOk()
|
||||
.expectBody(String.class).isEqualTo("returned posts");
|
||||
|
||||
verify(roleService, times(1)).getMonoRole(AnonymousUserConst.Role);
|
||||
verify(roleService).listDependenciesFlux(anySet());
|
||||
|
||||
webClient.get().uri("/apis/fake.halo.run/v1/posts/hello-halo").exchange()
|
||||
.expectStatus()
|
||||
.isUnauthorized();
|
||||
verify(roleService, times(2)).getMonoRole(AnonymousUserConst.Role);
|
||||
|
||||
verify(roleService, times(2)).listDependenciesFlux(anySet());
|
||||
}
|
||||
|
||||
@Test
|
||||
@WithMockUser(username = "user", roles = "post.read")
|
||||
void authenticatedUserAccessAuthenticationFreeApi() {
|
||||
when(roleService.getMonoRole("authenticated")).thenReturn(Mono.empty());
|
||||
when(roleService.getMonoRole("post.read")).thenReturn(Mono.empty());
|
||||
Role role = new Role();
|
||||
role.setMetadata(new Metadata());
|
||||
role.getMetadata().setName(AnonymousUserConst.Role);
|
||||
|
@ -166,11 +113,13 @@ class AuthorizationTest {
|
|||
.resources("posts")
|
||||
.build();
|
||||
role.getRules().add(policyRule);
|
||||
when(roleService.getMonoRole(AnonymousUserConst.Role))
|
||||
.thenReturn(Mono.just(role));
|
||||
|
||||
when(roleService.listDependenciesFlux(anySet())).thenReturn(Flux.just(role));
|
||||
|
||||
webClient.get().uri("/apis/fake.halo.run/v1/posts").exchange().expectStatus()
|
||||
.isOk()
|
||||
.expectBody(String.class).isEqualTo("returned posts");
|
||||
verify(roleService).listDependenciesFlux(anySet());
|
||||
}
|
||||
|
||||
@TestConfiguration
|
||||
|
|
|
@ -0,0 +1,113 @@
|
|||
package run.halo.app.security.authorization;
|
||||
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.mockito.Mockito.times;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
import static org.springframework.mock.http.server.reactive.MockServerHttpRequest.method;
|
||||
import static org.springframework.security.core.authority.AuthorityUtils.createAuthorityList;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.junit.jupiter.api.extension.ExtendWith;
|
||||
import org.mockito.InjectMocks;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.jupiter.MockitoExtension;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.security.core.userdetails.User;
|
||||
import reactor.core.publisher.Flux;
|
||||
import reactor.test.StepVerifier;
|
||||
import run.halo.app.core.extension.Role;
|
||||
import run.halo.app.core.extension.Role.PolicyRule;
|
||||
import run.halo.app.core.extension.service.RoleService;
|
||||
import run.halo.app.extension.Metadata;
|
||||
|
||||
@ExtendWith(MockitoExtension.class)
|
||||
class DefaultRuleResolverTest {
|
||||
|
||||
@Mock
|
||||
RoleService roleService;
|
||||
|
||||
@InjectMocks
|
||||
DefaultRuleResolver ruleResolver;
|
||||
|
||||
@Test
|
||||
void visitRules() {
|
||||
when(roleService.listDependenciesFlux(Set.of("authenticated", "anonymous", "ruleReadPost")))
|
||||
.thenReturn(Flux.just(mockRole()));
|
||||
var fakeUser = new User("admin", "123456", createAuthorityList("ruleReadPost"));
|
||||
var cases = getRequestResolveCases();
|
||||
cases.forEach(requestResolveCase -> {
|
||||
var httpMethod = HttpMethod.valueOf(requestResolveCase.method);
|
||||
var request = method(httpMethod, requestResolveCase.url).build();
|
||||
var requestInfo = RequestInfoFactory.INSTANCE.newRequestInfo(request);
|
||||
StepVerifier.create(ruleResolver.visitRules(fakeUser, requestInfo))
|
||||
.assertNext(
|
||||
visitor -> assertEquals(requestResolveCase.expected, visitor.isAllowed()))
|
||||
.verifyComplete();
|
||||
});
|
||||
|
||||
verify(roleService, times(cases.size())).listDependenciesFlux(
|
||||
Set.of("authenticated", "anonymous", "ruleReadPost"));
|
||||
}
|
||||
|
||||
Role mockRole() {
|
||||
var role = new Role();
|
||||
var rules = List.of(
|
||||
new PolicyRule.Builder().apiGroups("").resources("posts").verbs("list", "get").build(),
|
||||
new PolicyRule.Builder().apiGroups("").resources("categories").verbs("*").build(),
|
||||
new PolicyRule.Builder().apiGroups("api.plugin.halo.run")
|
||||
.resources("plugins/users")
|
||||
.resourceNames("foo/bar").verbs("*").build(),
|
||||
new PolicyRule.Builder().apiGroups("api.plugin.halo.run")
|
||||
.resources("plugins/users")
|
||||
.resourceNames("foo").verbs("*").build(),
|
||||
new PolicyRule.Builder().nonResourceURLs("/healthy").verbs("get", "post", "head")
|
||||
.build());
|
||||
role.setRules(rules);
|
||||
var metadata = new Metadata();
|
||||
metadata.setName("ruleReadPost");
|
||||
role.setMetadata(metadata);
|
||||
return role;
|
||||
}
|
||||
|
||||
List<RequestResolveCase> getRequestResolveCases() {
|
||||
return List.of(new RequestResolveCase("/api/v1/tags", "GET", false),
|
||||
new RequestResolveCase("/api/v1/tags/tagName", "GET", false),
|
||||
|
||||
new RequestResolveCase("/api/v1/categories/aName", "GET", true),
|
||||
new RequestResolveCase("/api/v1//categories", "POST", true),
|
||||
new RequestResolveCase("/api/v1/categories", "DELETE", true),
|
||||
new RequestResolveCase("/api/v1/posts", "GET", true),
|
||||
new RequestResolveCase("/api/v1/posts/aName", "GET", true),
|
||||
|
||||
new RequestResolveCase("/api/v1/posts", "DELETE", false),
|
||||
new RequestResolveCase("/api/v1/posts/aName", "UPDATE", false),
|
||||
|
||||
// group resource url
|
||||
new RequestResolveCase("/apis/group/v1/posts", "GET", false),
|
||||
|
||||
// plugin custom resource url
|
||||
new RequestResolveCase("/apis/api.plugin.halo.run/v1alpha1/plugins/foo/users", "GET",
|
||||
true),
|
||||
new RequestResolveCase("/apis/api.plugin.halo.run/v1alpha1/plugins/foo/users/bar",
|
||||
"GET", true),
|
||||
new RequestResolveCase("/apis/api.plugin.halo.run/v1alpha1/plugins/foo/posts/bar",
|
||||
"GET", false),
|
||||
|
||||
// non resource url
|
||||
new RequestResolveCase("/healthy", "GET", true),
|
||||
new RequestResolveCase("/healthy", "POST", true),
|
||||
new RequestResolveCase("/healthy", "HEAD", true),
|
||||
new RequestResolveCase("//healthy", "GET", false),
|
||||
new RequestResolveCase("/healthy/name", "GET", false),
|
||||
new RequestResolveCase("/healthy1", "GET", false),
|
||||
|
||||
new RequestResolveCase("//healthy//name", "GET", false),
|
||||
new RequestResolveCase("/", "GET", false));
|
||||
}
|
||||
|
||||
record RequestResolveCase(String url, String method, boolean expected) {
|
||||
}
|
||||
}
|
|
@ -3,33 +3,19 @@ package run.halo.app.security.authorization;
|
|||
import static org.assertj.core.api.Assertions.assertThat;
|
||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
||||
import static org.mockito.ArgumentMatchers.anyString;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.when;
|
||||
import static org.springframework.mock.http.server.reactive.MockServerHttpRequest.method;
|
||||
|
||||
import java.util.Collection;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import org.junit.jupiter.api.Test;
|
||||
import org.springframework.http.HttpMethod;
|
||||
import org.springframework.security.core.GrantedAuthority;
|
||||
import org.springframework.security.core.authority.AuthorityUtils;
|
||||
import org.springframework.security.core.userdetails.User;
|
||||
import run.halo.app.core.extension.Role;
|
||||
import run.halo.app.core.extension.Role.PolicyRule;
|
||||
import run.halo.app.core.extension.service.RoleService;
|
||||
import run.halo.app.extension.Metadata;
|
||||
|
||||
/**
|
||||
* Tests for {@link RequestInfoFactory}.
|
||||
*
|
||||
* @author guqing
|
||||
* @see RbacRequestEvaluation
|
||||
* @see RequestInfo
|
||||
* @see DefaultRuleResolver
|
||||
* @since 2.0.0
|
||||
*/
|
||||
public class RequestInfoResolverTest {
|
||||
|
@ -173,7 +159,7 @@ public class RequestInfoResolverTest {
|
|||
List.of(new ErrorCases("api resource has name and no subresource but post",
|
||||
"/api/version/themes/install"),
|
||||
new ErrorCases("apis resource has name and no subresource but post",
|
||||
"/apis/api.halo.run/v1alpha1/themes/install"));
|
||||
"/apis/api.halo.run/v1alpha1/themes/install"));
|
||||
for (ErrorCases errorCase : postCases) {
|
||||
var request =
|
||||
method(HttpMethod.POST, errorCase.url).build();
|
||||
|
@ -185,96 +171,13 @@ public class RequestInfoResolverTest {
|
|||
}
|
||||
}
|
||||
|
||||
@Test
|
||||
public void defaultRuleResolverTest() {
|
||||
var roleService = mock(RoleService.class);
|
||||
var ruleResolver = new DefaultRuleResolver(roleService);
|
||||
|
||||
Role role = new Role();
|
||||
List<PolicyRule> rules = List.of(
|
||||
new PolicyRule.Builder().apiGroups("").resources("posts").verbs("list", "get")
|
||||
.build(),
|
||||
new PolicyRule.Builder().apiGroups("").resources("categories").verbs("*").build(),
|
||||
new PolicyRule.Builder().apiGroups("api.plugin.halo.run").resources("plugins/users")
|
||||
.resourceNames("foo/bar").verbs("*").build(),
|
||||
new PolicyRule.Builder().apiGroups("api.plugin.halo.run").resources("plugins/users")
|
||||
.resourceNames("foo").verbs("*").build(),
|
||||
new PolicyRule.Builder().nonResourceURLs("/healthy").verbs("get", "post", "head")
|
||||
.build());
|
||||
role.setRules(rules);
|
||||
Metadata metadata = new Metadata();
|
||||
metadata.setName("ruleReadPost");
|
||||
role.setMetadata(metadata);
|
||||
|
||||
when(roleService.getRole(anyString())).thenReturn(role);
|
||||
|
||||
// list bound role names
|
||||
ruleResolver.setRoleBindingService(
|
||||
(Collection<? extends GrantedAuthority> authorities) -> Set.of("ruleReadPost"));
|
||||
|
||||
User user = new User("admin", "123456", AuthorityUtils.createAuthorityList("ruleReadPost"));
|
||||
|
||||
// resolve user rules
|
||||
List<PolicyRule> resolvedRules = ruleResolver.rulesFor(user);
|
||||
assertThat(resolvedRules).isNotNull();
|
||||
|
||||
RbacRequestEvaluation rbacRequestEvaluation = new RbacRequestEvaluation();
|
||||
for (RequestResolveCase requestResolveCase : getRequestResolveCases()) {
|
||||
var request =
|
||||
method(HttpMethod.valueOf(requestResolveCase.method),
|
||||
requestResolveCase.url).build();
|
||||
RequestInfo requestInfo = RequestInfoFactory.INSTANCE.newRequestInfo(request);
|
||||
|
||||
AttributesRecord attributes = new AttributesRecord(user, requestInfo);
|
||||
boolean allowed = rbacRequestEvaluation.rulesAllow(attributes, resolvedRules);
|
||||
assertThat(allowed).isEqualTo(requestResolveCase.expected);
|
||||
}
|
||||
}
|
||||
|
||||
public record NonApiCase(String url, boolean expected) {
|
||||
}
|
||||
|
||||
public record ErrorCases(String desc, String url) {
|
||||
}
|
||||
|
||||
List<RequestResolveCase> getRequestResolveCases() {
|
||||
return List.of(new RequestResolveCase("/api/v1/tags", "GET", false),
|
||||
new RequestResolveCase("/api/v1/tags/tagName", "GET", false),
|
||||
|
||||
new RequestResolveCase("/api/v1/categories/aName", "GET", true),
|
||||
new RequestResolveCase("/api/v1//categories", "POST", true),
|
||||
new RequestResolveCase("/api/v1/categories", "DELETE", true),
|
||||
new RequestResolveCase("/api/v1/posts", "GET", true),
|
||||
new RequestResolveCase("/api/v1/posts/aName", "GET", true),
|
||||
|
||||
new RequestResolveCase("/api/v1/posts", "DELETE", false),
|
||||
new RequestResolveCase("/api/v1/posts/aName", "UPDATE", false),
|
||||
|
||||
// group resource url
|
||||
new RequestResolveCase("/apis/group/v1/posts", "GET", false),
|
||||
|
||||
// plugin custom resource url
|
||||
new RequestResolveCase("/apis/api.plugin.halo.run/v1alpha1/plugins/foo/users", "GET",
|
||||
true),
|
||||
new RequestResolveCase("/apis/api.plugin.halo.run/v1alpha1/plugins/foo/users/bar",
|
||||
"GET", true),
|
||||
new RequestResolveCase("/apis/api.plugin.halo.run/v1alpha1/plugins/foo/posts/bar",
|
||||
"GET", false),
|
||||
|
||||
// non resource url
|
||||
new RequestResolveCase("/healthy", "GET", true),
|
||||
new RequestResolveCase("/healthy", "POST", true),
|
||||
new RequestResolveCase("/healthy", "HEAD", true),
|
||||
new RequestResolveCase("//healthy", "GET", false),
|
||||
new RequestResolveCase("/healthy/name", "GET", false),
|
||||
new RequestResolveCase("/healthy1", "GET", false),
|
||||
|
||||
new RequestResolveCase("//healthy//name", "GET", false),
|
||||
new RequestResolveCase("/", "GET", false));
|
||||
}
|
||||
|
||||
public record RequestResolveCase(String url, String method, boolean expected) {
|
||||
}
|
||||
|
||||
public record SuccessCase(String method, String url, String expectedVerb,
|
||||
String expectedAPIPrefix, String expectedAPIGroup,
|
||||
|
|
Loading…
Reference in New Issue