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;
|
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.ArraySchema;
|
||||||
import io.swagger.v3.oas.annotations.media.Schema;
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
import java.util.LinkedHashSet;
|
import java.util.LinkedHashSet;
|
||||||
|
@ -15,14 +17,14 @@ import run.halo.app.extension.GVK;
|
||||||
@GVK(group = "", version = "v1alpha1", kind = "Menu", plural = "menus", singular = "menu")
|
@GVK(group = "", version = "v1alpha1", kind = "Menu", plural = "menus", singular = "menu")
|
||||||
public class Menu extends AbstractExtension {
|
public class Menu extends AbstractExtension {
|
||||||
|
|
||||||
@Schema(description = "The spec of menu.", required = true)
|
@Schema(description = "The spec of menu.", requiredMode = REQUIRED)
|
||||||
private Spec spec;
|
private Spec spec;
|
||||||
|
|
||||||
@Data
|
@Data
|
||||||
@Schema(name = "MenuSpec")
|
@Schema(name = "MenuSpec")
|
||||||
public static class Spec {
|
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;
|
private String displayName;
|
||||||
|
|
||||||
@Schema(description = "Names of menu children below this menu.")
|
@Schema(description = "Names of menu children below this menu.")
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
package run.halo.app.core.extension;
|
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.JsonCreator;
|
||||||
import com.fasterxml.jackson.annotation.JsonValue;
|
import com.fasterxml.jackson.annotation.JsonValue;
|
||||||
import io.swagger.v3.oas.annotations.media.ArraySchema;
|
import io.swagger.v3.oas.annotations.media.ArraySchema;
|
||||||
|
@ -19,7 +21,7 @@ import run.halo.app.extension.Ref;
|
||||||
plural = "menuitems", singular = "menuitem")
|
plural = "menuitems", singular = "menuitem")
|
||||||
public class MenuItem extends AbstractExtension {
|
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;
|
private MenuItemSpec spec;
|
||||||
|
|
||||||
@Schema(description = "The status of menu item.")
|
@Schema(description = "The status of menu item.")
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
package run.halo.app.core.extension;
|
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 java.util.Arrays.compare;
|
||||||
import static run.halo.app.core.extension.Role.GROUP;
|
import static run.halo.app.core.extension.Role.GROUP;
|
||||||
import static run.halo.app.core.extension.Role.KIND;
|
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 VERSION = "v1alpha1";
|
||||||
public static final String KIND = "Role";
|
public static final String KIND = "Role";
|
||||||
|
|
||||||
@Schema(required = true)
|
@Schema(requiredMode = REQUIRED)
|
||||||
List<PolicyRule> rules;
|
List<PolicyRule> rules;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
package run.halo.app.core.extension;
|
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 io.swagger.v3.oas.annotations.media.Schema;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
|
@ -21,25 +23,25 @@ public class Setting extends AbstractExtension {
|
||||||
|
|
||||||
public static final String KIND = "Setting";
|
public static final String KIND = "Setting";
|
||||||
|
|
||||||
@Schema(required = true)
|
@Schema(requiredMode = REQUIRED)
|
||||||
private SettingSpec spec;
|
private SettingSpec spec;
|
||||||
|
|
||||||
@Data
|
@Data
|
||||||
public static class SettingSpec {
|
public static class SettingSpec {
|
||||||
|
|
||||||
@Schema(required = true, minLength = 1)
|
@Schema(requiredMode = REQUIRED, minLength = 1)
|
||||||
private List<SettingForm> forms;
|
private List<SettingForm> forms;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Data
|
@Data
|
||||||
public static class SettingForm {
|
public static class SettingForm {
|
||||||
|
|
||||||
@Schema(required = true)
|
@Schema(requiredMode = REQUIRED)
|
||||||
private String group;
|
private String group;
|
||||||
|
|
||||||
private String label;
|
private String label;
|
||||||
|
|
||||||
@Schema(required = true)
|
@Schema(requiredMode = REQUIRED)
|
||||||
private List<Object> formSchema;
|
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";
|
public static final String THEME_NAME_LABEL = "theme.halo.run/theme-name";
|
||||||
|
|
||||||
@Schema(required = true)
|
@Schema(requiredMode = REQUIRED)
|
||||||
private ThemeSpec spec;
|
private ThemeSpec spec;
|
||||||
|
|
||||||
private ThemeStatus status;
|
private ThemeStatus status;
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
package run.halo.app.core.extension;
|
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.GROUP;
|
||||||
import static run.halo.app.core.extension.User.KIND;
|
import static run.halo.app.core.extension.User.KIND;
|
||||||
import static run.halo.app.core.extension.User.VERSION;
|
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";
|
public static final String HIDDEN_USER_LABEL = "halo.run/hidden-user";
|
||||||
|
|
||||||
@Schema(required = true)
|
@Schema(requiredMode = REQUIRED)
|
||||||
private UserSpec spec;
|
private UserSpec spec;
|
||||||
|
|
||||||
private UserStatus status;
|
private UserStatus status;
|
||||||
|
@ -49,12 +50,12 @@ public class User extends AbstractExtension {
|
||||||
@Data
|
@Data
|
||||||
public static class UserSpec {
|
public static class UserSpec {
|
||||||
|
|
||||||
@Schema(required = true)
|
@Schema(requiredMode = REQUIRED)
|
||||||
private String displayName;
|
private String displayName;
|
||||||
|
|
||||||
private String avatar;
|
private String avatar;
|
||||||
|
|
||||||
@Schema(required = true)
|
@Schema(requiredMode = REQUIRED)
|
||||||
private String email;
|
private String email;
|
||||||
|
|
||||||
private String phone;
|
private String phone;
|
||||||
|
@ -87,16 +88,16 @@ public class User extends AbstractExtension {
|
||||||
@Data
|
@Data
|
||||||
public static class LoginHistory {
|
public static class LoginHistory {
|
||||||
|
|
||||||
@Schema(required = true)
|
@Schema(requiredMode = REQUIRED)
|
||||||
private Instant loginAt;
|
private Instant loginAt;
|
||||||
|
|
||||||
@Schema(required = true)
|
@Schema(requiredMode = REQUIRED)
|
||||||
private String sourceIp;
|
private String sourceIp;
|
||||||
|
|
||||||
@Schema(required = true)
|
@Schema(requiredMode = REQUIRED)
|
||||||
private String userAgent;
|
private String userAgent;
|
||||||
|
|
||||||
@Schema(required = true)
|
@Schema(requiredMode = REQUIRED)
|
||||||
private Boolean successful;
|
private Boolean successful;
|
||||||
|
|
||||||
private String reason;
|
private String reason;
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
package run.halo.app.core.extension.attachment;
|
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 static run.halo.app.core.extension.attachment.Attachment.KIND;
|
||||||
|
|
||||||
import io.swagger.v3.oas.annotations.media.ArraySchema;
|
import io.swagger.v3.oas.annotations.media.ArraySchema;
|
||||||
|
@ -20,7 +21,7 @@ public class Attachment extends AbstractExtension {
|
||||||
|
|
||||||
public static final String KIND = "Attachment";
|
public static final String KIND = "Attachment";
|
||||||
|
|
||||||
@Schema(required = true)
|
@Schema(requiredMode = REQUIRED)
|
||||||
private AttachmentSpec spec;
|
private AttachmentSpec spec;
|
||||||
|
|
||||||
private AttachmentStatus status;
|
private AttachmentStatus status;
|
||||||
|
|
|
@ -20,8 +20,6 @@ public enum Constant {
|
||||||
* Do not use this key to set external link. You could implement
|
* Do not use this key to set external link. You could implement
|
||||||
* {@link AttachmentHandler#getPermalink} by your self.
|
* {@link AttachmentHandler#getPermalink} by your self.
|
||||||
* <p>
|
* <p>
|
||||||
*
|
|
||||||
* @deprecated Use your own group instead.
|
|
||||||
*/
|
*/
|
||||||
public static final String EXTERNAL_LINK_ANNO_KEY = GROUP + "/external-link";
|
public static final String EXTERNAL_LINK_ANNO_KEY = GROUP + "/external-link";
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
package run.halo.app.core.extension.attachment;
|
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 static run.halo.app.core.extension.attachment.Group.KIND;
|
||||||
|
|
||||||
import io.swagger.v3.oas.annotations.media.Schema;
|
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 KIND = "Group";
|
||||||
public static final String HIDDEN_LABEL = "halo.run/hidden";
|
public static final String HIDDEN_LABEL = "halo.run/hidden";
|
||||||
|
|
||||||
@Schema(required = true)
|
@Schema(requiredMode = REQUIRED)
|
||||||
private GroupSpec spec;
|
private GroupSpec spec;
|
||||||
|
|
||||||
private GroupStatus status;
|
private GroupStatus status;
|
||||||
|
@ -28,7 +29,7 @@ public class Group extends AbstractExtension {
|
||||||
@Data
|
@Data
|
||||||
public static class GroupSpec {
|
public static class GroupSpec {
|
||||||
|
|
||||||
@Schema(required = true, description = "Display name of group")
|
@Schema(requiredMode = REQUIRED, description = "Display name of group")
|
||||||
private String displayName;
|
private String displayName;
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
package run.halo.app.core.extension.content;
|
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 static run.halo.app.core.extension.content.Category.KIND;
|
||||||
|
|
||||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||||
|
@ -28,7 +29,7 @@ public class Category extends AbstractExtension {
|
||||||
|
|
||||||
public static final GroupVersionKind GVK = GroupVersionKind.fromExtension(Category.class);
|
public static final GroupVersionKind GVK = GroupVersionKind.fromExtension(Category.class);
|
||||||
|
|
||||||
@Schema(required = true)
|
@Schema(requiredMode = REQUIRED)
|
||||||
private CategorySpec spec;
|
private CategorySpec spec;
|
||||||
|
|
||||||
@Schema
|
@Schema
|
||||||
|
@ -42,10 +43,10 @@ public class Category extends AbstractExtension {
|
||||||
@Data
|
@Data
|
||||||
public static class CategorySpec {
|
public static class CategorySpec {
|
||||||
|
|
||||||
@Schema(required = true, minLength = 1)
|
@Schema(requiredMode = REQUIRED, minLength = 1)
|
||||||
private String displayName;
|
private String displayName;
|
||||||
|
|
||||||
@Schema(required = true, minLength = 1)
|
@Schema(requiredMode = REQUIRED, minLength = 1)
|
||||||
private String slug;
|
private String slug;
|
||||||
|
|
||||||
private String description;
|
private String description;
|
||||||
|
@ -54,7 +55,7 @@ public class Category extends AbstractExtension {
|
||||||
|
|
||||||
private String template;
|
private String template;
|
||||||
|
|
||||||
@Schema(required = true, defaultValue = "0")
|
@Schema(requiredMode = REQUIRED, defaultValue = "0")
|
||||||
private Integer priority;
|
private Integer priority;
|
||||||
|
|
||||||
private List<String> children;
|
private List<String> children;
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
package run.halo.app.core.extension.content;
|
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 static org.apache.commons.lang3.ObjectUtils.defaultIfNull;
|
||||||
|
|
||||||
import com.fasterxml.jackson.annotation.JsonIgnore;
|
import com.fasterxml.jackson.annotation.JsonIgnore;
|
||||||
|
@ -30,7 +31,7 @@ public class Comment extends AbstractExtension {
|
||||||
|
|
||||||
public static final String KIND = "Comment";
|
public static final String KIND = "Comment";
|
||||||
|
|
||||||
@Schema(required = true)
|
@Schema(requiredMode = REQUIRED)
|
||||||
private CommentSpec spec;
|
private CommentSpec spec;
|
||||||
|
|
||||||
@Schema
|
@Schema
|
||||||
|
@ -49,7 +50,7 @@ public class Comment extends AbstractExtension {
|
||||||
@EqualsAndHashCode(callSuper = true)
|
@EqualsAndHashCode(callSuper = true)
|
||||||
public static class CommentSpec extends BaseCommentSpec {
|
public static class CommentSpec extends BaseCommentSpec {
|
||||||
|
|
||||||
@Schema(required = true)
|
@Schema(requiredMode = REQUIRED)
|
||||||
private Ref subjectRef;
|
private Ref subjectRef;
|
||||||
|
|
||||||
private Instant lastReadTime;
|
private Instant lastReadTime;
|
||||||
|
@ -58,13 +59,13 @@ public class Comment extends AbstractExtension {
|
||||||
@Data
|
@Data
|
||||||
public static class BaseCommentSpec {
|
public static class BaseCommentSpec {
|
||||||
|
|
||||||
@Schema(required = true, minLength = 1)
|
@Schema(requiredMode = REQUIRED, minLength = 1)
|
||||||
private String raw;
|
private String raw;
|
||||||
|
|
||||||
@Schema(required = true, minLength = 1)
|
@Schema(requiredMode = REQUIRED, minLength = 1)
|
||||||
private String content;
|
private String content;
|
||||||
|
|
||||||
@Schema(required = true)
|
@Schema(requiredMode = REQUIRED)
|
||||||
private CommentOwner owner;
|
private CommentOwner owner;
|
||||||
|
|
||||||
private String userAgent;
|
private String userAgent;
|
||||||
|
@ -78,19 +79,19 @@ public class Comment extends AbstractExtension {
|
||||||
*/
|
*/
|
||||||
private Instant creationTime;
|
private Instant creationTime;
|
||||||
|
|
||||||
@Schema(required = true, defaultValue = "0")
|
@Schema(requiredMode = REQUIRED, defaultValue = "0")
|
||||||
private Integer priority;
|
private Integer priority;
|
||||||
|
|
||||||
@Schema(required = true, defaultValue = "false")
|
@Schema(requiredMode = REQUIRED, defaultValue = "false")
|
||||||
private Boolean top;
|
private Boolean top;
|
||||||
|
|
||||||
@Schema(required = true, defaultValue = "true")
|
@Schema(requiredMode = REQUIRED, defaultValue = "true")
|
||||||
private Boolean allowNotification;
|
private Boolean allowNotification;
|
||||||
|
|
||||||
@Schema(required = true, defaultValue = "false")
|
@Schema(requiredMode = REQUIRED, defaultValue = "false")
|
||||||
private Boolean approved;
|
private Boolean approved;
|
||||||
|
|
||||||
@Schema(required = true, defaultValue = "false")
|
@Schema(requiredMode = REQUIRED, defaultValue = "false")
|
||||||
private Boolean hidden;
|
private Boolean hidden;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -100,10 +101,10 @@ public class Comment extends AbstractExtension {
|
||||||
public static final String AVATAR_ANNO = "avatar";
|
public static final String AVATAR_ANNO = "avatar";
|
||||||
public static final String WEBSITE_ANNO = "website";
|
public static final String WEBSITE_ANNO = "website";
|
||||||
|
|
||||||
@Schema(required = true, minLength = 1)
|
@Schema(requiredMode = REQUIRED, minLength = 1)
|
||||||
private String kind;
|
private String kind;
|
||||||
|
|
||||||
@Schema(required = true, maxLength = 64)
|
@Schema(requiredMode = REQUIRED, maxLength = 64)
|
||||||
private String name;
|
private String name;
|
||||||
|
|
||||||
private String displayName;
|
private String displayName;
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
package run.halo.app.core.extension.content;
|
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 io.swagger.v3.oas.annotations.media.Schema;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
import lombok.EqualsAndHashCode;
|
import lombok.EqualsAndHashCode;
|
||||||
|
@ -21,14 +23,14 @@ public class Reply extends AbstractExtension {
|
||||||
|
|
||||||
public static final String KIND = "Reply";
|
public static final String KIND = "Reply";
|
||||||
|
|
||||||
@Schema(required = true)
|
@Schema(requiredMode = REQUIRED)
|
||||||
private ReplySpec spec;
|
private ReplySpec spec;
|
||||||
|
|
||||||
@Data
|
@Data
|
||||||
@EqualsAndHashCode(callSuper = true)
|
@EqualsAndHashCode(callSuper = true)
|
||||||
public static class ReplySpec extends Comment.BaseCommentSpec {
|
public static class ReplySpec extends Comment.BaseCommentSpec {
|
||||||
|
|
||||||
@Schema(required = true, minLength = 1)
|
@Schema(requiredMode = REQUIRED, minLength = 1)
|
||||||
private String commentName;
|
private String commentName;
|
||||||
|
|
||||||
private String quoteReply;
|
private String quoteReply;
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
package run.halo.app.core.extension.content;
|
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 com.fasterxml.jackson.annotation.JsonIgnore;
|
||||||
import io.swagger.v3.oas.annotations.media.Schema;
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
import java.time.Instant;
|
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 OWNER_LABEL = "content.halo.run/owner";
|
||||||
public static final String VISIBLE_LABEL = "content.halo.run/visible";
|
public static final String VISIBLE_LABEL = "content.halo.run/visible";
|
||||||
|
|
||||||
@Schema(required = true)
|
@Schema(requiredMode = REQUIRED)
|
||||||
private SinglePageSpec spec;
|
private SinglePageSpec spec;
|
||||||
|
|
||||||
@Schema
|
@Schema
|
||||||
|
@ -58,10 +60,10 @@ public class SinglePage extends AbstractExtension {
|
||||||
|
|
||||||
@Data
|
@Data
|
||||||
public static class SinglePageSpec {
|
public static class SinglePageSpec {
|
||||||
@Schema(required = true, minLength = 1)
|
@Schema(requiredMode = REQUIRED, minLength = 1)
|
||||||
private String title;
|
private String title;
|
||||||
|
|
||||||
@Schema(required = true, minLength = 1)
|
@Schema(requiredMode = REQUIRED, minLength = 1)
|
||||||
private String slug;
|
private String slug;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -79,27 +81,27 @@ public class SinglePage extends AbstractExtension {
|
||||||
|
|
||||||
private String cover;
|
private String cover;
|
||||||
|
|
||||||
@Schema(required = true, defaultValue = "false")
|
@Schema(requiredMode = REQUIRED, defaultValue = "false")
|
||||||
private Boolean deleted;
|
private Boolean deleted;
|
||||||
|
|
||||||
@Schema(required = true, defaultValue = "false")
|
@Schema(requiredMode = REQUIRED, defaultValue = "false")
|
||||||
private Boolean publish;
|
private Boolean publish;
|
||||||
|
|
||||||
private Instant publishTime;
|
private Instant publishTime;
|
||||||
|
|
||||||
@Schema(required = true, defaultValue = "false")
|
@Schema(requiredMode = REQUIRED, defaultValue = "false")
|
||||||
private Boolean pinned;
|
private Boolean pinned;
|
||||||
|
|
||||||
@Schema(required = true, defaultValue = "true")
|
@Schema(requiredMode = REQUIRED, defaultValue = "true")
|
||||||
private Boolean allowComment;
|
private Boolean allowComment;
|
||||||
|
|
||||||
@Schema(required = true, defaultValue = "PUBLIC")
|
@Schema(requiredMode = REQUIRED, defaultValue = "PUBLIC")
|
||||||
private Post.VisibleEnum visible;
|
private Post.VisibleEnum visible;
|
||||||
|
|
||||||
@Schema(required = true, defaultValue = "0")
|
@Schema(requiredMode = REQUIRED, defaultValue = "0")
|
||||||
private Integer priority;
|
private Integer priority;
|
||||||
|
|
||||||
@Schema(required = true)
|
@Schema(requiredMode = REQUIRED)
|
||||||
private Post.Excerpt excerpt;
|
private Post.Excerpt excerpt;
|
||||||
|
|
||||||
private List<Map<String, String>> htmlMetas;
|
private List<Map<String, String>> htmlMetas;
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
package run.halo.app.core.extension.content;
|
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 io.swagger.v3.oas.annotations.media.Schema;
|
||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
import java.util.LinkedHashSet;
|
import java.util.LinkedHashSet;
|
||||||
|
@ -26,19 +28,19 @@ public class Snapshot extends AbstractExtension {
|
||||||
public static final String KIND = "Snapshot";
|
public static final String KIND = "Snapshot";
|
||||||
public static final String KEEP_RAW_ANNO = "content.halo.run/keep-raw";
|
public static final String KEEP_RAW_ANNO = "content.halo.run/keep-raw";
|
||||||
|
|
||||||
@Schema(required = true)
|
@Schema(requiredMode = REQUIRED)
|
||||||
private SnapShotSpec spec;
|
private SnapShotSpec spec;
|
||||||
|
|
||||||
@Data
|
@Data
|
||||||
public static class SnapShotSpec {
|
public static class SnapShotSpec {
|
||||||
|
|
||||||
@Schema(required = true)
|
@Schema(requiredMode = REQUIRED)
|
||||||
private Ref subjectRef;
|
private Ref subjectRef;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* such as: markdown | html | json | asciidoc | latex.
|
* 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 rawType;
|
||||||
|
|
||||||
private String rawPatch;
|
private String rawPatch;
|
||||||
|
@ -49,7 +51,7 @@ public class Snapshot extends AbstractExtension {
|
||||||
|
|
||||||
private Instant lastModifyTime;
|
private Instant lastModifyTime;
|
||||||
|
|
||||||
@Schema(required = true, minLength = 1)
|
@Schema(requiredMode = REQUIRED, minLength = 1)
|
||||||
private String owner;
|
private String owner;
|
||||||
|
|
||||||
private Set<String> contributors;
|
private Set<String> contributors;
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
package run.halo.app.core.extension.content;
|
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 com.fasterxml.jackson.annotation.JsonIgnore;
|
||||||
import io.swagger.v3.oas.annotations.media.Schema;
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
|
@ -25,7 +27,7 @@ public class Tag extends AbstractExtension {
|
||||||
|
|
||||||
public static final GroupVersionKind GVK = GroupVersionKind.fromExtension(Tag.class);
|
public static final GroupVersionKind GVK = GroupVersionKind.fromExtension(Tag.class);
|
||||||
|
|
||||||
@Schema(required = true)
|
@Schema(requiredMode = REQUIRED)
|
||||||
private TagSpec spec;
|
private TagSpec spec;
|
||||||
|
|
||||||
@Schema
|
@Schema
|
||||||
|
@ -34,10 +36,10 @@ public class Tag extends AbstractExtension {
|
||||||
@Data
|
@Data
|
||||||
public static class TagSpec {
|
public static class TagSpec {
|
||||||
|
|
||||||
@Schema(required = true, minLength = 1)
|
@Schema(requiredMode = REQUIRED, minLength = 1)
|
||||||
private String displayName;
|
private String displayName;
|
||||||
|
|
||||||
@Schema(required = true, minLength = 1)
|
@Schema(requiredMode = REQUIRED, minLength = 1)
|
||||||
private String slug;
|
private String slug;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
package run.halo.app.extension;
|
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.JsonIgnore;
|
||||||
import com.fasterxml.jackson.annotation.JsonProperty;
|
import com.fasterxml.jackson.annotation.JsonProperty;
|
||||||
import io.swagger.v3.oas.annotations.media.Schema;
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
@ -13,7 +15,7 @@ import org.springframework.util.StringUtils;
|
||||||
*/
|
*/
|
||||||
public interface ExtensionOperator {
|
public interface ExtensionOperator {
|
||||||
|
|
||||||
@Schema(required = true)
|
@Schema(requiredMode = REQUIRED)
|
||||||
@JsonProperty("apiVersion")
|
@JsonProperty("apiVersion")
|
||||||
default String getApiVersion() {
|
default String getApiVersion() {
|
||||||
final var gvk = getClass().getAnnotation(GVK.class);
|
final var gvk = getClass().getAnnotation(GVK.class);
|
||||||
|
@ -27,7 +29,7 @@ public interface ExtensionOperator {
|
||||||
return gvk.version();
|
return gvk.version();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Schema(required = true)
|
@Schema(requiredMode = REQUIRED)
|
||||||
@JsonProperty("kind")
|
@JsonProperty("kind")
|
||||||
default String getKind() {
|
default String getKind() {
|
||||||
final var gvk = getClass().getAnnotation(GVK.class);
|
final var gvk = getClass().getAnnotation(GVK.class);
|
||||||
|
@ -38,7 +40,7 @@ public interface ExtensionOperator {
|
||||||
return gvk.kind();
|
return gvk.kind();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Schema(required = true, implementation = Metadata.class)
|
@Schema(requiredMode = REQUIRED, implementation = Metadata.class)
|
||||||
@JsonProperty("metadata")
|
@JsonProperty("metadata")
|
||||||
MetadataOperator getMetadata();
|
MetadataOperator getMetadata();
|
||||||
|
|
||||||
|
@ -48,30 +50,6 @@ public interface ExtensionOperator {
|
||||||
|
|
||||||
void setMetadata(MetadataOperator metadata);
|
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.
|
* Sets GroupVersionKind of the Extension.
|
||||||
*
|
*
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
package run.halo.app.extension;
|
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.annotation.JsonProperty;
|
||||||
import io.swagger.v3.oas.annotations.media.Schema;
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
import java.util.ArrayList;
|
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>> {
|
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 "
|
@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;
|
private final int page;
|
||||||
|
|
||||||
@Schema(description = "Size of each page. If not set or equal to 0, it means no pagination.",
|
@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;
|
private final int size;
|
||||||
|
|
||||||
@Schema(description = "Total elements.", required = true)
|
@Schema(description = "Total elements.", requiredMode = REQUIRED)
|
||||||
private final long total;
|
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;
|
private final List<T> items;
|
||||||
|
|
||||||
public ListResult(int page, int size, long total, 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);
|
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() {
|
public boolean isFirst() {
|
||||||
return !hasPrevious();
|
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() {
|
public boolean isLast() {
|
||||||
return !hasNext();
|
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")
|
@JsonProperty("hasNext")
|
||||||
public boolean hasNext() {
|
public boolean hasNext() {
|
||||||
if (page <= 0) {
|
if (page <= 0) {
|
||||||
|
@ -69,7 +74,8 @@ public class ListResult<T> implements Iterable<T>, Supplier<Stream<T>> {
|
||||||
return page < getTotalPages();
|
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")
|
@JsonProperty("hasPrevious")
|
||||||
public boolean hasPrevious() {
|
public boolean hasPrevious() {
|
||||||
return page > 1;
|
return page > 1;
|
||||||
|
@ -80,7 +86,7 @@ public class ListResult<T> implements Iterable<T>, Supplier<Stream<T>> {
|
||||||
return items.iterator();
|
return items.iterator();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Schema(description = "Indicates total pages.", required = true)
|
@Schema(description = "Indicates total pages.", requiredMode = REQUIRED)
|
||||||
@JsonProperty("totalPages")
|
@JsonProperty("totalPages")
|
||||||
public long getTotalPages() {
|
public long getTotalPages() {
|
||||||
return size == 0 ? 1 : (total + size - 1) / size;
|
return size == 0 ? 1 : (total + size - 1) / size;
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
package run.halo.app.extension;
|
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.annotation.JsonProperty;
|
||||||
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
|
import com.fasterxml.jackson.databind.annotation.JsonDeserialize;
|
||||||
import io.swagger.v3.oas.annotations.media.Schema;
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
|
@ -17,7 +19,7 @@ import java.util.Set;
|
||||||
@Schema(implementation = Metadata.class)
|
@Schema(implementation = Metadata.class)
|
||||||
public interface MetadataOperator {
|
public interface MetadataOperator {
|
||||||
|
|
||||||
@Schema(name = "name", description = "Metadata name", required = true)
|
@Schema(name = "name", description = "Metadata name", requiredMode = REQUIRED)
|
||||||
@JsonProperty("name")
|
@JsonProperty("name")
|
||||||
String getName();
|
String getName();
|
||||||
|
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
package run.halo.app.extension;
|
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 io.swagger.v3.oas.annotations.media.Schema;
|
||||||
import java.util.Objects;
|
import java.util.Objects;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
|
@ -17,7 +19,7 @@ public class Ref {
|
||||||
@Schema(description = "Extension kind")
|
@Schema(description = "Extension kind")
|
||||||
private String 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;
|
private String name;
|
||||||
|
|
||||||
public static Ref of(String name) {
|
public static Ref of(String name) {
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
package run.halo.app.infra;
|
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 io.swagger.v3.oas.annotations.media.Schema;
|
||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
import lombok.AllArgsConstructor;
|
import lombok.AllArgsConstructor;
|
||||||
|
@ -28,7 +30,7 @@ public class Condition {
|
||||||
* example: Ready, Initialized.
|
* example: Ready, Initialized.
|
||||||
* maxLength: 316.
|
* 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])?)*/)?("
|
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])$")
|
+ "([A-Za-z0-9][-A-Za-z0-9_.]*)?[A-Za-z0-9])$")
|
||||||
private String type;
|
private String type;
|
||||||
|
@ -36,26 +38,26 @@ public class Condition {
|
||||||
/**
|
/**
|
||||||
* Status is the status of the condition. Can be True, False, Unknown.
|
* Status is the status of the condition. Can be True, False, Unknown.
|
||||||
*/
|
*/
|
||||||
@Schema(required = true)
|
@Schema(requiredMode = REQUIRED)
|
||||||
private ConditionStatus status;
|
private ConditionStatus status;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Last time the condition transitioned from one status to another.
|
* Last time the condition transitioned from one status to another.
|
||||||
*/
|
*/
|
||||||
@Schema(required = true)
|
@Schema(requiredMode = REQUIRED)
|
||||||
private Instant lastTransitionTime;
|
private Instant lastTransitionTime;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Human-readable message indicating details about last transition.
|
* Human-readable message indicating details about last transition.
|
||||||
* This may be an empty string.
|
* This may be an empty string.
|
||||||
*/
|
*/
|
||||||
@Schema(required = true, maxLength = 32768)
|
@Schema(requiredMode = REQUIRED, maxLength = 32768)
|
||||||
private String message;
|
private String message;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Unique, one-word, CamelCase reason for the condition's last transition.
|
* 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_])?$")
|
pattern = "^[A-Za-z]([A-Za-z0-9_,:]*[A-Za-z0-9_])?$")
|
||||||
private String reason;
|
private String reason;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
package run.halo.app.search;
|
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 io.swagger.v3.oas.annotations.media.Schema;
|
||||||
import org.springframework.util.MultiValueMap;
|
import org.springframework.util.MultiValueMap;
|
||||||
import org.springframework.util.StringUtils;
|
import org.springframework.util.StringUtils;
|
||||||
|
@ -17,7 +19,7 @@ public class SearchParam {
|
||||||
this.query = query;
|
this.query = query;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Schema(name = "keyword", required = true)
|
@Schema(name = "keyword", requiredMode = REQUIRED)
|
||||||
public String getKeyword() {
|
public String getKeyword() {
|
||||||
var keyword = query.getFirst("keyword");
|
var keyword = query.getFirst("keyword");
|
||||||
if (!StringUtils.hasText(keyword)) {
|
if (!StringUtils.hasText(keyword)) {
|
||||||
|
|
|
@ -61,8 +61,9 @@ public class WebServerSecurityConfig {
|
||||||
|
|
||||||
http.securityMatcher(pathMatchers("/api/**", "/apis/**", "/oauth2/**",
|
http.securityMatcher(pathMatchers("/api/**", "/apis/**", "/oauth2/**",
|
||||||
"/login/**", "/logout", "/actuator/**"))
|
"/login/**", "/logout", "/actuator/**"))
|
||||||
.authorizeExchange().anyExchange()
|
.authorizeExchange(spec -> {
|
||||||
.access(new RequestInfoAuthorizationManager(roleService)).and()
|
spec.anyExchange().access(new RequestInfoAuthorizationManager(roleService));
|
||||||
|
})
|
||||||
.anonymous(spec -> {
|
.anonymous(spec -> {
|
||||||
spec.authorities(AnonymousUserConst.Role);
|
spec.authorities(AnonymousUserConst.Role);
|
||||||
spec.principal(AnonymousUserConst.PRINCIPAL);
|
spec.principal(AnonymousUserConst.PRINCIPAL);
|
||||||
|
@ -85,19 +86,24 @@ public class WebServerSecurityConfig {
|
||||||
var mediaTypeMatcher = new MediaTypeServerWebExchangeMatcher(MediaType.TEXT_HTML);
|
var mediaTypeMatcher = new MediaTypeServerWebExchangeMatcher(MediaType.TEXT_HTML);
|
||||||
mediaTypeMatcher.setIgnoredMediaTypes(Set.of(MediaType.ALL));
|
mediaTypeMatcher.setIgnoredMediaTypes(Set.of(MediaType.ALL));
|
||||||
http.securityMatcher(new AndServerWebExchangeMatcher(pathMatcher, mediaTypeMatcher))
|
http.securityMatcher(new AndServerWebExchangeMatcher(pathMatcher, mediaTypeMatcher))
|
||||||
.authorizeExchange().anyExchange().permitAll().and()
|
|
||||||
.securityContextRepository(securityContextRepository)
|
.securityContextRepository(securityContextRepository)
|
||||||
.headers()
|
.authorizeExchange(spec -> {
|
||||||
.frameOptions(spec -> {
|
spec.anyExchange().permitAll();
|
||||||
var frameOptions = haloProperties.getSecurity().getFrameOptions();
|
|
||||||
spec.mode(frameOptions.getMode());
|
|
||||||
if (frameOptions.isDisabled()) {
|
|
||||||
spec.disable();
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
.referrerPolicy(
|
.headers(headerSpec -> headerSpec
|
||||||
spec -> spec.policy(haloProperties.getSecurity().getReferrerOptions().getPolicy()))
|
.frameOptions(frameSpec -> {
|
||||||
.cache().disable().and()
|
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(
|
.anonymous(spec -> spec.authenticationFilter(
|
||||||
new HaloAnonymousAuthenticationWebFilter("portal", AnonymousUserConst.PRINCIPAL,
|
new HaloAnonymousAuthenticationWebFilter("portal", AnonymousUserConst.PRINCIPAL,
|
||||||
AuthorityUtils.createAuthorityList(AnonymousUserConst.Role),
|
AuthorityUtils.createAuthorityList(AnonymousUserConst.Role),
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
package run.halo.app.content;
|
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 io.swagger.v3.oas.annotations.media.Schema;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import org.apache.commons.lang3.StringUtils;
|
||||||
|
@ -11,11 +13,11 @@ import run.halo.app.extension.Ref;
|
||||||
* @author guqing
|
* @author guqing
|
||||||
* @since 2.0.0
|
* @since 2.0.0
|
||||||
*/
|
*/
|
||||||
public record ContentRequest(@Schema(required = true) Ref subjectRef,
|
public record ContentRequest(@Schema(requiredMode = REQUIRED) Ref subjectRef,
|
||||||
String headSnapshotName,
|
String headSnapshotName,
|
||||||
@Schema(required = true) String raw,
|
@Schema(requiredMode = REQUIRED) String raw,
|
||||||
@Schema(required = true) String content,
|
@Schema(requiredMode = REQUIRED) String content,
|
||||||
@Schema(required = true) String rawType) {
|
@Schema(requiredMode = REQUIRED) String rawType) {
|
||||||
|
|
||||||
public Snapshot toSnapshot() {
|
public Snapshot toSnapshot() {
|
||||||
final Snapshot snapshot = new Snapshot();
|
final Snapshot snapshot = new Snapshot();
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
package run.halo.app.content;
|
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 io.swagger.v3.oas.annotations.media.Schema;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
|
@ -17,21 +19,21 @@ import run.halo.app.core.extension.content.Tag;
|
||||||
@Data
|
@Data
|
||||||
public class ListedPost {
|
public class ListedPost {
|
||||||
|
|
||||||
@Schema(required = true)
|
@Schema(requiredMode = REQUIRED)
|
||||||
private Post post;
|
private Post post;
|
||||||
|
|
||||||
@Schema(required = true)
|
@Schema(requiredMode = REQUIRED)
|
||||||
private List<Category> categories;
|
private List<Category> categories;
|
||||||
|
|
||||||
@Schema(required = true)
|
@Schema(requiredMode = REQUIRED)
|
||||||
private List<Tag> tags;
|
private List<Tag> tags;
|
||||||
|
|
||||||
@Schema(required = true)
|
@Schema(requiredMode = REQUIRED)
|
||||||
private List<Contributor> contributors;
|
private List<Contributor> contributors;
|
||||||
|
|
||||||
@Schema(required = true)
|
@Schema(requiredMode = REQUIRED)
|
||||||
private Contributor owner;
|
private Contributor owner;
|
||||||
|
|
||||||
@Schema(required = true)
|
@Schema(requiredMode = REQUIRED)
|
||||||
private Stats stats;
|
private Stats stats;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
package run.halo.app.content;
|
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 io.swagger.v3.oas.annotations.media.Schema;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
|
@ -15,15 +17,15 @@ import run.halo.app.core.extension.content.SinglePage;
|
||||||
@Data
|
@Data
|
||||||
public class ListedSinglePage {
|
public class ListedSinglePage {
|
||||||
|
|
||||||
@Schema(required = true)
|
@Schema(requiredMode = REQUIRED)
|
||||||
private SinglePage page;
|
private SinglePage page;
|
||||||
|
|
||||||
@Schema(required = true)
|
@Schema(requiredMode = REQUIRED)
|
||||||
private List<Contributor> contributors;
|
private List<Contributor> contributors;
|
||||||
|
|
||||||
@Schema(required = true)
|
@Schema(requiredMode = REQUIRED)
|
||||||
private Contributor owner;
|
private Contributor owner;
|
||||||
|
|
||||||
@Schema(required = true)
|
@Schema(requiredMode = REQUIRED)
|
||||||
private Stats stats;
|
private Stats stats;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
package run.halo.app.content;
|
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 io.swagger.v3.oas.annotations.media.Schema;
|
||||||
import run.halo.app.core.extension.content.Post;
|
import run.halo.app.core.extension.content.Post;
|
||||||
import run.halo.app.extension.Ref;
|
import run.halo.app.extension.Ref;
|
||||||
|
@ -8,8 +10,8 @@ import run.halo.app.extension.Ref;
|
||||||
* @author guqing
|
* @author guqing
|
||||||
* @since 2.0.0
|
* @since 2.0.0
|
||||||
*/
|
*/
|
||||||
public record PostRequest(@Schema(required = true) Post post,
|
public record PostRequest(@Schema(requiredMode = REQUIRED) Post post,
|
||||||
@Schema(required = true) Content content) {
|
@Schema(requiredMode = REQUIRED) Content content) {
|
||||||
|
|
||||||
public ContentRequest contentRequest() {
|
public ContentRequest contentRequest() {
|
||||||
Ref subjectRef = Ref.of(post);
|
Ref subjectRef = Ref.of(post);
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
package run.halo.app.content;
|
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 io.swagger.v3.oas.annotations.media.Schema;
|
||||||
import run.halo.app.core.extension.content.SinglePage;
|
import run.halo.app.core.extension.content.SinglePage;
|
||||||
import run.halo.app.extension.Ref;
|
import run.halo.app.extension.Ref;
|
||||||
|
@ -10,8 +12,8 @@ import run.halo.app.extension.Ref;
|
||||||
* @author guqing
|
* @author guqing
|
||||||
* @since 2.0.0
|
* @since 2.0.0
|
||||||
*/
|
*/
|
||||||
public record SinglePageRequest(@Schema(required = true) SinglePage page,
|
public record SinglePageRequest(@Schema(requiredMode = REQUIRED) SinglePage page,
|
||||||
@Schema(required = true) Content content) {
|
@Schema(requiredMode = REQUIRED) Content content) {
|
||||||
|
|
||||||
public ContentRequest contentRequest() {
|
public ContentRequest contentRequest() {
|
||||||
Ref subjectRef = Ref.of(page);
|
Ref subjectRef = Ref.of(page);
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
package run.halo.app.content.comment;
|
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 io.swagger.v3.oas.annotations.media.Schema;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
|
@ -16,15 +18,15 @@ import run.halo.app.extension.Ref;
|
||||||
@Data
|
@Data
|
||||||
public class CommentRequest {
|
public class CommentRequest {
|
||||||
|
|
||||||
@Schema(required = true)
|
@Schema(requiredMode = REQUIRED)
|
||||||
private Ref subjectRef;
|
private Ref subjectRef;
|
||||||
|
|
||||||
private CommentEmailOwner owner;
|
private CommentEmailOwner owner;
|
||||||
|
|
||||||
@Schema(required = true, minLength = 1)
|
@Schema(requiredMode = REQUIRED, minLength = 1)
|
||||||
private String raw;
|
private String raw;
|
||||||
|
|
||||||
@Schema(required = true, minLength = 1)
|
@Schema(requiredMode = REQUIRED, minLength = 1)
|
||||||
private String content;
|
private String content;
|
||||||
|
|
||||||
@Schema(defaultValue = "false")
|
@Schema(defaultValue = "false")
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
package run.halo.app.content.comment;
|
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 io.swagger.v3.oas.annotations.media.Schema;
|
||||||
import lombok.Builder;
|
import lombok.Builder;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
|
@ -16,14 +18,14 @@ import run.halo.app.extension.Extension;
|
||||||
@Builder
|
@Builder
|
||||||
public class ListedComment {
|
public class ListedComment {
|
||||||
|
|
||||||
@Schema(required = true)
|
@Schema(requiredMode = REQUIRED)
|
||||||
private Comment comment;
|
private Comment comment;
|
||||||
|
|
||||||
@Schema(required = true)
|
@Schema(requiredMode = REQUIRED)
|
||||||
private OwnerInfo owner;
|
private OwnerInfo owner;
|
||||||
|
|
||||||
private Extension subject;
|
private Extension subject;
|
||||||
|
|
||||||
@Schema(required = true)
|
@Schema(requiredMode = REQUIRED)
|
||||||
private CommentStats stats;
|
private CommentStats stats;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
package run.halo.app.content.comment;
|
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 io.swagger.v3.oas.annotations.media.Schema;
|
||||||
import lombok.Builder;
|
import lombok.Builder;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
|
@ -15,12 +17,12 @@ import run.halo.app.core.extension.content.Reply;
|
||||||
@Builder
|
@Builder
|
||||||
public class ListedReply {
|
public class ListedReply {
|
||||||
|
|
||||||
@Schema(required = true)
|
@Schema(requiredMode = REQUIRED)
|
||||||
private Reply reply;
|
private Reply reply;
|
||||||
|
|
||||||
@Schema(required = true)
|
@Schema(requiredMode = REQUIRED)
|
||||||
private OwnerInfo owner;
|
private OwnerInfo owner;
|
||||||
|
|
||||||
@Schema(required = true)
|
@Schema(requiredMode = REQUIRED)
|
||||||
private CommentStats stats;
|
private CommentStats stats;
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,7 @@
|
||||||
package run.halo.app.content.comment;
|
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 io.swagger.v3.oas.annotations.media.Schema;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
|
@ -15,10 +17,10 @@ import run.halo.app.extension.Metadata;
|
||||||
@Data
|
@Data
|
||||||
public class ReplyRequest {
|
public class ReplyRequest {
|
||||||
|
|
||||||
@Schema(required = true, minLength = 1)
|
@Schema(requiredMode = REQUIRED, minLength = 1)
|
||||||
private String raw;
|
private String raw;
|
||||||
|
|
||||||
@Schema(required = true, minLength = 1)
|
@Schema(requiredMode = REQUIRED, minLength = 1)
|
||||||
private String content;
|
private String content;
|
||||||
|
|
||||||
@Schema(defaultValue = "false")
|
@Schema(defaultValue = "false")
|
||||||
|
|
|
@ -1,5 +1,6 @@
|
||||||
package run.halo.app.core.extension.attachment.endpoint;
|
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 java.util.Comparator.comparing;
|
||||||
import static org.springdoc.core.fn.builders.apiresponse.Builder.responseBuilder;
|
import static org.springdoc.core.fn.builders.apiresponse.Builder.responseBuilder;
|
||||||
import static org.springdoc.core.fn.builders.content.Builder.contentBuilder;
|
import static org.springdoc.core.fn.builders.content.Builder.contentBuilder;
|
||||||
|
@ -274,10 +275,10 @@ public class AttachmentEndpoint implements CustomEndpoint {
|
||||||
|
|
||||||
public interface IUploadRequest {
|
public interface IUploadRequest {
|
||||||
|
|
||||||
@Schema(required = true, description = "Attachment file")
|
@Schema(requiredMode = REQUIRED, description = "Attachment file")
|
||||||
FilePart getFile();
|
FilePart getFile();
|
||||||
|
|
||||||
@Schema(required = true, description = "Storage policy name")
|
@Schema(requiredMode = REQUIRED, description = "Storage policy name")
|
||||||
String getPolicyName();
|
String getPolicyName();
|
||||||
|
|
||||||
@Schema(description = "The name of the group to which the attachment belongs")
|
@Schema(description = "The name of the group to which the attachment belongs")
|
||||||
|
|
|
@ -429,7 +429,7 @@ public class UserEndpoint implements CustomEndpoint {
|
||||||
}
|
}
|
||||||
|
|
||||||
record ChangePasswordRequest(
|
record ChangePasswordRequest(
|
||||||
@Schema(description = "New password.", required = true, minLength = 6)
|
@Schema(description = "New password.", requiredMode = REQUIRED, minLength = 6)
|
||||||
String password) {
|
String password) {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -522,8 +522,8 @@ public class UserEndpoint implements CustomEndpoint {
|
||||||
.flatMapIterable(Function.identity());
|
.flatMapIterable(Function.identity());
|
||||||
}
|
}
|
||||||
|
|
||||||
record UserPermission(@Schema(required = true) Set<Role> roles,
|
record UserPermission(@Schema(requiredMode = REQUIRED) Set<Role> roles,
|
||||||
@Schema(required = true) Set<String> uiPermissions) {
|
@Schema(requiredMode = REQUIRED) Set<String> uiPermissions) {
|
||||||
}
|
}
|
||||||
|
|
||||||
public class ListRequest extends IListRequest.QueryListRequest {
|
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 static org.apache.commons.lang3.ObjectUtils.defaultIfNull;
|
||||||
|
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.file.Path;
|
|
||||||
import java.time.Instant;
|
import java.time.Instant;
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.List;
|
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.Condition;
|
||||||
import run.halo.app.infra.ConditionStatus;
|
import run.halo.app.infra.ConditionStatus;
|
||||||
import run.halo.app.infra.SystemVersionSupplier;
|
import run.halo.app.infra.SystemVersionSupplier;
|
||||||
|
import run.halo.app.infra.ThemeRootGetter;
|
||||||
import run.halo.app.infra.exception.ThemeUninstallException;
|
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.JsonUtils;
|
||||||
import run.halo.app.infra.utils.VersionUtils;
|
import run.halo.app.infra.utils.VersionUtils;
|
||||||
import run.halo.app.theme.ThemePathPolicy;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Reconciler for theme.
|
* Reconciler for theme.
|
||||||
|
@ -45,7 +43,8 @@ public class ThemeReconciler implements Reconciler<Request> {
|
||||||
private static final String FINALIZER_NAME = "theme-protection";
|
private static final String FINALIZER_NAME = "theme-protection";
|
||||||
|
|
||||||
private final ExtensionClient client;
|
private final ExtensionClient client;
|
||||||
private final ThemePathPolicy themePathPolicy;
|
|
||||||
|
private final ThemeRootGetter themeRoot;
|
||||||
private final SystemVersionSupplier systemVersionSupplier;
|
private final SystemVersionSupplier systemVersionSupplier;
|
||||||
|
|
||||||
private final RetryTemplate retryTemplate = RetryTemplate.builder()
|
private final RetryTemplate retryTemplate = RetryTemplate.builder()
|
||||||
|
@ -54,10 +53,10 @@ public class ThemeReconciler implements Reconciler<Request> {
|
||||||
.retryOn(IllegalStateException.class)
|
.retryOn(IllegalStateException.class)
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
public ThemeReconciler(ExtensionClient client, HaloProperties haloProperties,
|
public ThemeReconciler(ExtensionClient client, ThemeRootGetter themeRoot,
|
||||||
SystemVersionSupplier systemVersionSupplier) {
|
SystemVersionSupplier systemVersionSupplier) {
|
||||||
this.client = client;
|
this.client = client;
|
||||||
themePathPolicy = new ThemePathPolicy(haloProperties.getWorkDir());
|
this.themeRoot = themeRoot;
|
||||||
this.systemVersionSupplier = systemVersionSupplier;
|
this.systemVersionSupplier = systemVersionSupplier;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -90,7 +89,7 @@ public class ThemeReconciler implements Reconciler<Request> {
|
||||||
final Theme.ThemeStatus oldStatus = JsonUtils.deepCopy(status);
|
final Theme.ThemeStatus oldStatus = JsonUtils.deepCopy(status);
|
||||||
theme.setStatus(status);
|
theme.setStatus(status);
|
||||||
|
|
||||||
Path themePath = themePathPolicy.generate(theme);
|
var themePath = themeRoot.get().resolve(name);
|
||||||
status.setLocation(themePath.toAbsolutePath().toString());
|
status.setLocation(themePath.toAbsolutePath().toString());
|
||||||
|
|
||||||
status.setPhase(Theme.ThemePhase.READY);
|
status.setPhase(Theme.ThemePhase.READY);
|
||||||
|
@ -216,7 +215,7 @@ public class ThemeReconciler implements Reconciler<Request> {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void deleteThemeFiles(Theme theme) {
|
private void deleteThemeFiles(Theme theme) {
|
||||||
Path themeDir = themePathPolicy.generate(theme);
|
var themeDir = themeRoot.get().resolve(theme.getMetadata().getName());
|
||||||
try {
|
try {
|
||||||
FileSystemUtils.deleteRecursively(themeDir);
|
FileSystemUtils.deleteRecursively(themeDir);
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
|
|
|
@ -3,14 +3,10 @@ package run.halo.app.core.extension.service;
|
||||||
import static run.halo.app.extension.MetadataUtil.nullSafeLabels;
|
import static run.halo.app.extension.MetadataUtil.nullSafeLabels;
|
||||||
|
|
||||||
import com.fasterxml.jackson.core.type.TypeReference;
|
import com.fasterxml.jackson.core.type.TypeReference;
|
||||||
import java.util.ArrayDeque;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.Comparator;
|
import java.util.Comparator;
|
||||||
import java.util.Deque;
|
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import java.util.function.Predicate;
|
import java.util.function.Predicate;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
|
@ -19,7 +15,6 @@ import org.apache.commons.lang3.StringUtils;
|
||||||
import org.springframework.lang.NonNull;
|
import org.springframework.lang.NonNull;
|
||||||
import org.springframework.stereotype.Service;
|
import org.springframework.stereotype.Service;
|
||||||
import reactor.core.publisher.Flux;
|
import reactor.core.publisher.Flux;
|
||||||
import reactor.core.publisher.Mono;
|
|
||||||
import run.halo.app.core.extension.Role;
|
import run.halo.app.core.extension.Role;
|
||||||
import run.halo.app.core.extension.RoleBinding;
|
import run.halo.app.core.extension.RoleBinding;
|
||||||
import run.halo.app.core.extension.RoleBinding.RoleRef;
|
import run.halo.app.core.extension.RoleBinding.RoleRef;
|
||||||
|
@ -42,17 +37,6 @@ public class DefaultRoleService implements RoleService {
|
||||||
this.extensionClient = extensionClient;
|
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
|
@Override
|
||||||
public Flux<RoleRef> listRoleRefs(Subject subject) {
|
public Flux<RoleRef> listRoleRefs(Subject subject) {
|
||||||
return extensionClient.list(RoleBinding.class,
|
return extensionClient.list(RoleBinding.class,
|
||||||
|
@ -61,39 +45,6 @@ public class DefaultRoleService implements RoleService {
|
||||||
.map(RoleBinding::getRoleRef);
|
.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
|
@Override
|
||||||
public Flux<Role> listDependenciesFlux(Set<String> names) {
|
public Flux<Role> listDependenciesFlux(Set<String> names) {
|
||||||
if (names == null) {
|
if (names == null) {
|
||||||
|
|
|
@ -1,10 +1,7 @@
|
||||||
package run.halo.app.core.extension.service;
|
package run.halo.app.core.extension.service;
|
||||||
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import org.springframework.lang.NonNull;
|
|
||||||
import reactor.core.publisher.Flux;
|
import reactor.core.publisher.Flux;
|
||||||
import reactor.core.publisher.Mono;
|
|
||||||
import run.halo.app.core.extension.Role;
|
import run.halo.app.core.extension.Role;
|
||||||
import run.halo.app.core.extension.RoleBinding.RoleRef;
|
import run.halo.app.core.extension.RoleBinding.RoleRef;
|
||||||
import run.halo.app.core.extension.RoleBinding.Subject;
|
import run.halo.app.core.extension.RoleBinding.Subject;
|
||||||
|
@ -15,16 +12,8 @@ import run.halo.app.core.extension.RoleBinding.Subject;
|
||||||
*/
|
*/
|
||||||
public interface RoleService {
|
public interface RoleService {
|
||||||
|
|
||||||
@NonNull
|
|
||||||
@Deprecated
|
|
||||||
Role getRole(String name);
|
|
||||||
|
|
||||||
Mono<Role> getMonoRole(String name);
|
|
||||||
|
|
||||||
Flux<RoleRef> listRoleRefs(Subject subject);
|
Flux<RoleRef> listRoleRefs(Subject subject);
|
||||||
|
|
||||||
List<Role> listDependencies(Set<String> names);
|
|
||||||
|
|
||||||
Flux<Role> listDependenciesFlux(Set<String> names);
|
Flux<Role> listDependenciesFlux(Set<String> names);
|
||||||
|
|
||||||
Flux<Role> list(Set<String> roleNames);
|
Flux<Role> list(Set<String> roleNames);
|
||||||
|
|
|
@ -410,7 +410,7 @@ public class ThemeEndpoint implements CustomEndpoint {
|
||||||
|
|
||||||
public interface IUpgradeRequest {
|
public interface IUpgradeRequest {
|
||||||
|
|
||||||
@Schema(required = true, description = "Theme zip file.")
|
@Schema(requiredMode = REQUIRED, description = "Theme zip file.")
|
||||||
FilePart getFile();
|
FilePart getFile();
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -501,7 +501,7 @@ public class ThemeEndpoint implements CustomEndpoint {
|
||||||
|
|
||||||
@Schema(name = "ThemeInstallRequest")
|
@Schema(name = "ThemeInstallRequest")
|
||||||
public record InstallRequest(
|
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) {
|
public record InstallFromUriRequest(@Schema(requiredMode = REQUIRED) URI uri) {
|
||||||
|
|
|
@ -102,6 +102,7 @@ public class ReactiveExtensionClientImpl implements ReactiveExtensionClient {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
public <E extends Extension> Mono<E> create(E extension) {
|
public <E extends Extension> Mono<E> create(E extension) {
|
||||||
return Mono.just(extension)
|
return Mono.just(extension)
|
||||||
.doOnNext(ext -> {
|
.doOnNext(ext -> {
|
||||||
|
@ -133,6 +134,7 @@ public class ReactiveExtensionClientImpl implements ReactiveExtensionClient {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
public <E extends Extension> Mono<E> update(E extension) {
|
public <E extends Extension> Mono<E> update(E extension) {
|
||||||
// Refactor the atomic reference if we have a better solution.
|
// Refactor the atomic reference if we have a better solution.
|
||||||
final var statusChangeOnly = new AtomicBoolean(false);
|
final var statusChangeOnly = new AtomicBoolean(false);
|
||||||
|
@ -181,6 +183,7 @@ public class ReactiveExtensionClientImpl implements ReactiveExtensionClient {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
public <E extends Extension> Mono<E> delete(E extension) {
|
public <E extends Extension> Mono<E> delete(E extension) {
|
||||||
// set deletionTimestamp
|
// set deletionTimestamp
|
||||||
extension.getMetadata().setDeletionTimestamp(Instant.now());
|
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;
|
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 io.swagger.v3.oas.annotations.media.Schema;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
import lombok.EqualsAndHashCode;
|
import lombok.EqualsAndHashCode;
|
||||||
|
@ -15,7 +17,7 @@ import run.halo.app.extension.Ref;
|
||||||
plural = "searchengines", singular = "searchengine")
|
plural = "searchengines", singular = "searchengine")
|
||||||
public class SearchEngine extends AbstractExtension {
|
public class SearchEngine extends AbstractExtension {
|
||||||
|
|
||||||
@Schema(required = true)
|
@Schema(requiredMode = REQUIRED)
|
||||||
private SearchEngineSpec spec;
|
private SearchEngineSpec spec;
|
||||||
|
|
||||||
@Data
|
@Data
|
||||||
|
@ -25,7 +27,7 @@ public class SearchEngine extends AbstractExtension {
|
||||||
|
|
||||||
private String website;
|
private String website;
|
||||||
|
|
||||||
@Schema(required = true)
|
@Schema(requiredMode = REQUIRED)
|
||||||
private String displayName;
|
private String displayName;
|
||||||
|
|
||||||
private String description;
|
private String description;
|
||||||
|
|
|
@ -1,9 +1,9 @@
|
||||||
package run.halo.app.security;
|
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 static org.springframework.security.web.server.util.matcher.ServerWebExchangeMatchers.pathMatchers;
|
||||||
|
|
||||||
import org.springframework.security.config.web.server.ServerHttpSecurity;
|
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.CsrfWebFilter;
|
||||||
import org.springframework.security.web.server.csrf.ServerCsrfTokenRequestAttributeHandler;
|
import org.springframework.security.web.server.csrf.ServerCsrfTokenRequestAttributeHandler;
|
||||||
import org.springframework.security.web.server.util.matcher.AndServerWebExchangeMatcher;
|
import org.springframework.security.web.server.util.matcher.AndServerWebExchangeMatcher;
|
||||||
|
@ -20,12 +20,12 @@ public class CsrfConfigurer implements SecurityConfigurer {
|
||||||
CsrfWebFilter.DEFAULT_CSRF_MATCHER,
|
CsrfWebFilter.DEFAULT_CSRF_MATCHER,
|
||||||
new NegatedServerWebExchangeMatcher(pathMatchers("/api/**", "/apis/**")
|
new NegatedServerWebExchangeMatcher(pathMatchers("/api/**", "/apis/**")
|
||||||
));
|
));
|
||||||
|
http.csrf(csrfSpec -> csrfSpec
|
||||||
http.csrf().csrfTokenRepository(CookieServerCsrfTokenRepository.withHttpOnlyFalse())
|
.csrfTokenRepository(withHttpOnlyFalse())
|
||||||
// TODO Use XorServerCsrfTokenRequestAttributeHandler instead when console implements
|
// TODO Use XorServerCsrfTokenRequestAttributeHandler instead when console implements
|
||||||
// the algorithm
|
// the algorithm
|
||||||
.csrfTokenRequestHandler(new ServerCsrfTokenRequestAttributeHandler())
|
.csrfTokenRequestHandler(new ServerCsrfTokenRequestAttributeHandler())
|
||||||
.requireCsrfProtectionMatcher(csrfMatcher);
|
.requireCsrfProtectionMatcher(csrfMatcher));
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -9,28 +9,5 @@ import reactor.core.publisher.Mono;
|
||||||
*/
|
*/
|
||||||
public interface AuthorizationRuleResolver {
|
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);
|
Mono<AuthorizingVisitor> visitRules(UserDetails user, RequestInfo requestInfo);
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,27 +1,17 @@
|
||||||
package run.halo.app.security.authorization;
|
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.HashSet;
|
||||||
import java.util.List;
|
|
||||||
import java.util.Set;
|
|
||||||
import java.util.concurrent.atomic.AtomicBoolean;
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
import lombok.extern.slf4j.Slf4j;
|
import lombok.extern.slf4j.Slf4j;
|
||||||
import org.apache.commons.lang3.StringUtils;
|
|
||||||
import org.springframework.security.core.userdetails.UserDetails;
|
import org.springframework.security.core.userdetails.UserDetails;
|
||||||
import org.springframework.util.Assert;
|
import org.springframework.util.Assert;
|
||||||
import org.springframework.util.CollectionUtils;
|
import org.springframework.util.CollectionUtils;
|
||||||
import reactor.core.publisher.Mono;
|
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.DefaultRoleBindingService;
|
||||||
import run.halo.app.core.extension.service.RoleBindingService;
|
import run.halo.app.core.extension.service.RoleBindingService;
|
||||||
import run.halo.app.core.extension.service.RoleService;
|
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.AnonymousUserConst;
|
||||||
import run.halo.app.infra.utils.JsonUtils;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author guqing
|
* @author guqing
|
||||||
|
@ -33,57 +23,11 @@ public class DefaultRuleResolver implements AuthorizationRuleResolver {
|
||||||
private static final String AUTHENTICATED_ROLE = "authenticated";
|
private static final String AUTHENTICATED_ROLE = "authenticated";
|
||||||
private RoleService roleService;
|
private RoleService roleService;
|
||||||
|
|
||||||
private RoleBindingService roleBindingService = new DefaultRoleBindingService();
|
private RoleBindingService roleBindingService;
|
||||||
|
|
||||||
public DefaultRuleResolver(RoleService roleService) {
|
public DefaultRuleResolver(RoleService roleService) {
|
||||||
this.roleService = roleService;
|
this.roleService = roleService;
|
||||||
}
|
this.roleBindingService = new DefaultRoleBindingService();
|
||||||
|
|
||||||
@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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -123,32 +67,6 @@ public class DefaultRuleResolver implements AuthorizationRuleResolver {
|
||||||
.then(Mono.just(visitor));
|
.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) {
|
String roleBindingDescriber(String roleName, String subject) {
|
||||||
return String.format("Binding role [%s] to [%s]", roleName, 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;
|
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 io.swagger.v3.oas.annotations.media.Schema;
|
||||||
import lombok.Builder;
|
import lombok.Builder;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
|
@ -21,16 +23,16 @@ import run.halo.app.extension.MetadataOperator;
|
||||||
@EqualsAndHashCode
|
@EqualsAndHashCode
|
||||||
public class ReplyVo implements ExtensionVoOperator {
|
public class ReplyVo implements ExtensionVoOperator {
|
||||||
|
|
||||||
@Schema(required = true)
|
@Schema(requiredMode = REQUIRED)
|
||||||
private MetadataOperator metadata;
|
private MetadataOperator metadata;
|
||||||
|
|
||||||
@Schema(required = true)
|
@Schema(requiredMode = REQUIRED)
|
||||||
private Reply.ReplySpec spec;
|
private Reply.ReplySpec spec;
|
||||||
|
|
||||||
@Schema(required = true)
|
@Schema(requiredMode = REQUIRED)
|
||||||
private OwnerInfo owner;
|
private OwnerInfo owner;
|
||||||
|
|
||||||
@Schema(required = true)
|
@Schema(requiredMode = REQUIRED)
|
||||||
private CommentStatsVo stats;
|
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.assertNotNull;
|
||||||
import static org.junit.jupiter.api.Assertions.assertTrue;
|
import static org.junit.jupiter.api.Assertions.assertTrue;
|
||||||
import static org.mockito.ArgumentMatchers.anySet;
|
import static org.mockito.ArgumentMatchers.anySet;
|
||||||
import static org.mockito.ArgumentMatchers.anyString;
|
|
||||||
import static org.mockito.Mockito.when;
|
import static org.mockito.Mockito.when;
|
||||||
import static org.springframework.security.test.web.reactive.server.SecurityMockServerConfigurers.csrf;
|
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.security.test.context.support.WithMockUser;
|
||||||
import org.springframework.test.web.reactive.server.WebTestClient;
|
import org.springframework.test.web.reactive.server.WebTestClient;
|
||||||
import reactor.core.publisher.Flux;
|
import reactor.core.publisher.Flux;
|
||||||
import reactor.core.publisher.Mono;
|
|
||||||
import run.halo.app.core.extension.Role;
|
import run.halo.app.core.extension.Role;
|
||||||
import run.halo.app.core.extension.service.RoleService;
|
import run.halo.app.core.extension.service.RoleService;
|
||||||
import run.halo.app.extension.ExtensionClient;
|
import run.halo.app.extension.ExtensionClient;
|
||||||
|
@ -60,7 +58,6 @@ class ExtensionConfigurationTest {
|
||||||
role.setMetadata(new Metadata());
|
role.setMetadata(new Metadata());
|
||||||
role.getMetadata().setName("supper-role");
|
role.getMetadata().setName("supper-role");
|
||||||
role.setRules(List.of(rule));
|
role.setRules(List.of(rule));
|
||||||
when(roleService.getMonoRole(anyString())).thenReturn(Mono.just(role));
|
|
||||||
when(roleService.listDependenciesFlux(anySet())).thenReturn(Flux.just(role));
|
when(roleService.listDependenciesFlux(anySet())).thenReturn(Flux.just(role));
|
||||||
// register scheme
|
// register scheme
|
||||||
schemeManager.register(FakeExtension.class);
|
schemeManager.register(FakeExtension.class);
|
||||||
|
|
|
@ -17,13 +17,11 @@ import org.springframework.http.MediaType;
|
||||||
import org.springframework.security.test.context.support.WithMockUser;
|
import org.springframework.security.test.context.support.WithMockUser;
|
||||||
import org.springframework.test.web.reactive.server.WebTestClient;
|
import org.springframework.test.web.reactive.server.WebTestClient;
|
||||||
import reactor.core.publisher.Flux;
|
import reactor.core.publisher.Flux;
|
||||||
import reactor.core.publisher.Mono;
|
|
||||||
import run.halo.app.core.extension.Role;
|
import run.halo.app.core.extension.Role;
|
||||||
import run.halo.app.core.extension.content.Post;
|
import run.halo.app.core.extension.content.Post;
|
||||||
import run.halo.app.core.extension.service.RoleService;
|
import run.halo.app.core.extension.service.RoleService;
|
||||||
import run.halo.app.extension.Metadata;
|
import run.halo.app.extension.Metadata;
|
||||||
import run.halo.app.extension.MetadataOperator;
|
import run.halo.app.extension.MetadataOperator;
|
||||||
import run.halo.app.extension.ReactiveExtensionClient;
|
|
||||||
import run.halo.app.infra.utils.JsonUtils;
|
import run.halo.app.infra.utils.JsonUtils;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -44,9 +42,6 @@ public class PostIntegrationTests {
|
||||||
@MockBean
|
@MockBean
|
||||||
RoleService roleService;
|
RoleService roleService;
|
||||||
|
|
||||||
@Autowired
|
|
||||||
ReactiveExtensionClient client;
|
|
||||||
|
|
||||||
@BeforeEach
|
@BeforeEach
|
||||||
void setUp() {
|
void setUp() {
|
||||||
var rule = new Role.PolicyRule.Builder()
|
var rule = new Role.PolicyRule.Builder()
|
||||||
|
@ -58,7 +53,6 @@ public class PostIntegrationTests {
|
||||||
role.setMetadata(new Metadata());
|
role.setMetadata(new Metadata());
|
||||||
role.getMetadata().setName("super-role");
|
role.getMetadata().setName("super-role");
|
||||||
role.setRules(List.of(rule));
|
role.setRules(List.of(rule));
|
||||||
when(roleService.getMonoRole("authenticated")).thenReturn(Mono.just(role));
|
|
||||||
when(roleService.listDependenciesFlux(anySet())).thenReturn(Flux.just(role));
|
when(roleService.listDependenciesFlux(anySet())).thenReturn(Flux.just(role));
|
||||||
webTestClient = webTestClient.mutateWith(csrf());
|
webTestClient = webTestClient.mutateWith(csrf());
|
||||||
}
|
}
|
||||||
|
|
|
@ -81,17 +81,8 @@ class UserEndpointTest {
|
||||||
@BeforeEach
|
@BeforeEach
|
||||||
void setUp() {
|
void setUp() {
|
||||||
// disable authorization
|
// disable authorization
|
||||||
var rule = new Role.PolicyRule.Builder()
|
webClient = WebTestClient.bindToRouterFunction(endpoint.endpoint()).build()
|
||||||
.apiGroups("*")
|
.mutateWith(csrf());
|
||||||
.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());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nested
|
@Nested
|
||||||
|
|
|
@ -19,11 +19,12 @@ import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Optional;
|
import java.util.Optional;
|
||||||
import org.json.JSONException;
|
import org.json.JSONException;
|
||||||
import org.junit.jupiter.api.AfterEach;
|
|
||||||
import org.junit.jupiter.api.BeforeEach;
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.junit.jupiter.api.extension.ExtendWith;
|
import org.junit.jupiter.api.extension.ExtendWith;
|
||||||
|
import org.junit.jupiter.api.io.TempDir;
|
||||||
import org.mockito.ArgumentCaptor;
|
import org.mockito.ArgumentCaptor;
|
||||||
|
import org.mockito.InjectMocks;
|
||||||
import org.mockito.Mock;
|
import org.mockito.Mock;
|
||||||
import org.mockito.junit.jupiter.MockitoExtension;
|
import org.mockito.junit.jupiter.MockitoExtension;
|
||||||
import org.mockito.stubbing.Answer;
|
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.MetadataOperator;
|
||||||
import run.halo.app.extension.controller.Reconciler;
|
import run.halo.app.extension.controller.Reconciler;
|
||||||
import run.halo.app.infra.SystemVersionSupplier;
|
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.infra.utils.JsonUtils;
|
||||||
import run.halo.app.theme.ThemePathPolicy;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tests for {@link ThemeReconciler}.
|
* Tests for {@link ThemeReconciler}.
|
||||||
|
@ -57,37 +57,31 @@ class ThemeReconcilerTest {
|
||||||
private ExtensionClient extensionClient;
|
private ExtensionClient extensionClient;
|
||||||
|
|
||||||
@Mock
|
@Mock
|
||||||
private HaloProperties haloProperties;
|
private SystemVersionSupplier systemVersionSupplier;
|
||||||
|
|
||||||
@Mock
|
@Mock
|
||||||
private SystemVersionSupplier systemVersionSupplier;
|
ThemeRootGetter themeRoot;
|
||||||
|
|
||||||
@Mock
|
@Mock
|
||||||
private File defaultTheme;
|
private File defaultTheme;
|
||||||
|
|
||||||
|
@InjectMocks
|
||||||
|
ThemeReconciler themeReconciler;
|
||||||
|
|
||||||
|
@TempDir
|
||||||
private Path tempDirectory;
|
private Path tempDirectory;
|
||||||
|
|
||||||
@BeforeEach
|
@BeforeEach
|
||||||
void setUp() throws IOException {
|
void setUp() throws IOException {
|
||||||
tempDirectory = Files.createTempDirectory("halo-theme-");
|
|
||||||
defaultTheme = ResourceUtils.getFile("classpath:themes/default");
|
defaultTheme = ResourceUtils.getFile("classpath:themes/default");
|
||||||
lenient().when(systemVersionSupplier.get()).thenReturn(Version.valueOf("0.0.0"));
|
lenient().when(systemVersionSupplier.get()).thenReturn(Version.valueOf("0.0.0"));
|
||||||
}
|
}
|
||||||
|
|
||||||
@AfterEach
|
|
||||||
void tearDown() throws IOException {
|
|
||||||
FileSystemUtils.deleteRecursively(tempDirectory);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
void reconcileDelete() throws IOException {
|
void reconcileDelete() throws IOException {
|
||||||
Path testWorkDir = tempDirectory.resolve("reconcile-delete");
|
Path testWorkDir = tempDirectory.resolve("reconcile-delete");
|
||||||
Files.createDirectory(testWorkDir);
|
Files.createDirectory(testWorkDir);
|
||||||
when(haloProperties.getWorkDir()).thenReturn(testWorkDir);
|
when(themeRoot.get()).thenReturn(testWorkDir);
|
||||||
|
|
||||||
final ThemeReconciler themeReconciler =
|
|
||||||
new ThemeReconciler(extensionClient, haloProperties, systemVersionSupplier);
|
|
||||||
final ThemePathPolicy themePathPolicy = new ThemePathPolicy(testWorkDir);
|
|
||||||
|
|
||||||
Theme theme = new Theme();
|
Theme theme = new Theme();
|
||||||
Metadata metadata = new Metadata();
|
Metadata metadata = new Metadata();
|
||||||
|
@ -100,7 +94,7 @@ class ThemeReconcilerTest {
|
||||||
themeSpec.setSettingName("theme-test-setting");
|
themeSpec.setSettingName("theme-test-setting");
|
||||||
theme.setSpec(themeSpec);
|
theme.setSpec(themeSpec);
|
||||||
|
|
||||||
Path defaultThemePath = themePathPolicy.generate(theme);
|
Path defaultThemePath = testWorkDir.resolve("theme-test");
|
||||||
|
|
||||||
// copy to temp directory
|
// copy to temp directory
|
||||||
FileSystemUtils.copyRecursively(defaultTheme.toPath(), defaultThemePath);
|
FileSystemUtils.copyRecursively(defaultTheme.toPath(), defaultThemePath);
|
||||||
|
@ -130,10 +124,10 @@ class ThemeReconcilerTest {
|
||||||
final MetadataOperator metadata = theme.getMetadata();
|
final MetadataOperator metadata = theme.getMetadata();
|
||||||
|
|
||||||
Path testWorkDir = tempDirectory.resolve("reconcile-delete");
|
Path testWorkDir = tempDirectory.resolve("reconcile-delete");
|
||||||
when(haloProperties.getWorkDir()).thenReturn(testWorkDir);
|
when(themeRoot.get()).thenReturn(testWorkDir);
|
||||||
|
|
||||||
final ThemeReconciler themeReconciler =
|
final ThemeReconciler themeReconciler =
|
||||||
new ThemeReconciler(extensionClient, haloProperties, systemVersionSupplier);
|
new ThemeReconciler(extensionClient, themeRoot, systemVersionSupplier);
|
||||||
|
|
||||||
final int[] retryFlags = {0, 0};
|
final int[] retryFlags = {0, 0};
|
||||||
when(extensionClient.fetch(eq(Setting.class), eq("theme-test-setting")))
|
when(extensionClient.fetch(eq(Setting.class), eq("theme-test-setting")))
|
||||||
|
@ -169,10 +163,10 @@ class ThemeReconcilerTest {
|
||||||
Theme theme = fakeTheme();
|
Theme theme = fakeTheme();
|
||||||
|
|
||||||
Path testWorkDir = tempDirectory.resolve("reconcile-delete");
|
Path testWorkDir = tempDirectory.resolve("reconcile-delete");
|
||||||
when(haloProperties.getWorkDir()).thenReturn(testWorkDir);
|
when(themeRoot.get()).thenReturn(testWorkDir);
|
||||||
|
|
||||||
final ThemeReconciler themeReconciler =
|
final ThemeReconciler themeReconciler =
|
||||||
new ThemeReconciler(extensionClient, haloProperties, systemVersionSupplier);
|
new ThemeReconciler(extensionClient, themeRoot, systemVersionSupplier);
|
||||||
|
|
||||||
final int[] retryFlags = {0};
|
final int[] retryFlags = {0};
|
||||||
when(extensionClient.fetch(eq(Setting.class), eq("theme-test-setting")))
|
when(extensionClient.fetch(eq(Setting.class), eq("theme-test-setting")))
|
||||||
|
@ -198,10 +192,10 @@ class ThemeReconcilerTest {
|
||||||
void reconcileStatus() {
|
void reconcileStatus() {
|
||||||
when(systemVersionSupplier.get()).thenReturn(Version.valueOf("2.3.0"));
|
when(systemVersionSupplier.get()).thenReturn(Version.valueOf("2.3.0"));
|
||||||
Path testWorkDir = tempDirectory.resolve("reconcile-delete");
|
Path testWorkDir = tempDirectory.resolve("reconcile-delete");
|
||||||
when(haloProperties.getWorkDir()).thenReturn(testWorkDir);
|
when(themeRoot.get()).thenReturn(testWorkDir);
|
||||||
|
|
||||||
final ThemeReconciler themeReconciler =
|
final ThemeReconciler themeReconciler =
|
||||||
new ThemeReconciler(extensionClient, haloProperties, systemVersionSupplier);
|
new ThemeReconciler(extensionClient, themeRoot, systemVersionSupplier);
|
||||||
Theme theme = fakeTheme();
|
Theme theme = fakeTheme();
|
||||||
theme.setStatus(null);
|
theme.setStatus(null);
|
||||||
theme.getSpec().setRequires(">2.3.0");
|
theme.getSpec().setRequires(">2.3.0");
|
||||||
|
@ -246,10 +240,7 @@ class ThemeReconcilerTest {
|
||||||
void themeSettingDefaultValue() throws IOException, JSONException {
|
void themeSettingDefaultValue() throws IOException, JSONException {
|
||||||
Path testWorkDir = tempDirectory.resolve("reconcile-setting-value");
|
Path testWorkDir = tempDirectory.resolve("reconcile-setting-value");
|
||||||
Files.createDirectory(testWorkDir);
|
Files.createDirectory(testWorkDir);
|
||||||
when(haloProperties.getWorkDir()).thenReturn(testWorkDir);
|
when(themeRoot.get()).thenReturn(testWorkDir);
|
||||||
|
|
||||||
final ThemeReconciler themeReconciler =
|
|
||||||
new ThemeReconciler(extensionClient, haloProperties, systemVersionSupplier);
|
|
||||||
|
|
||||||
Theme theme = new Theme();
|
Theme theme = new Theme();
|
||||||
Metadata metadata = new Metadata();
|
Metadata metadata = new Metadata();
|
||||||
|
|
|
@ -1,9 +1,7 @@
|
||||||
package run.halo.app.core.extension.service;
|
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.any;
|
||||||
import static org.mockito.ArgumentMatchers.anyString;
|
import static org.mockito.ArgumentMatchers.anyString;
|
||||||
import static org.mockito.ArgumentMatchers.same;
|
|
||||||
import static org.mockito.Mockito.eq;
|
import static org.mockito.Mockito.eq;
|
||||||
import static org.mockito.Mockito.lenient;
|
import static org.mockito.Mockito.lenient;
|
||||||
import static org.mockito.Mockito.times;
|
import static org.mockito.Mockito.times;
|
||||||
|
@ -17,10 +15,10 @@ import java.util.Set;
|
||||||
import java.util.function.Predicate;
|
import java.util.function.Predicate;
|
||||||
import java.util.stream.Stream;
|
import java.util.stream.Stream;
|
||||||
import org.assertj.core.api.AssertionsForInterfaceTypes;
|
import org.assertj.core.api.AssertionsForInterfaceTypes;
|
||||||
import org.junit.jupiter.api.BeforeEach;
|
|
||||||
import org.junit.jupiter.api.Nested;
|
import org.junit.jupiter.api.Nested;
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.junit.jupiter.api.extension.ExtendWith;
|
import org.junit.jupiter.api.extension.ExtendWith;
|
||||||
|
import org.mockito.InjectMocks;
|
||||||
import org.mockito.Mock;
|
import org.mockito.Mock;
|
||||||
import org.mockito.junit.jupiter.MockitoExtension;
|
import org.mockito.junit.jupiter.MockitoExtension;
|
||||||
import reactor.core.publisher.Flux;
|
import reactor.core.publisher.Flux;
|
||||||
|
@ -28,7 +26,6 @@ import reactor.core.publisher.Mono;
|
||||||
import reactor.test.StepVerifier;
|
import reactor.test.StepVerifier;
|
||||||
import run.halo.app.core.extension.Role;
|
import run.halo.app.core.extension.Role;
|
||||||
import run.halo.app.core.extension.RoleBinding;
|
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.Metadata;
|
||||||
import run.halo.app.extension.ReactiveExtensionClient;
|
import run.halo.app.extension.ReactiveExtensionClient;
|
||||||
import run.halo.app.infra.utils.JsonUtils;
|
import run.halo.app.infra.utils.JsonUtils;
|
||||||
|
@ -44,56 +41,9 @@ class DefaultRoleServiceTest {
|
||||||
@Mock
|
@Mock
|
||||||
private ReactiveExtensionClient extensionClient;
|
private ReactiveExtensionClient extensionClient;
|
||||||
|
|
||||||
|
@InjectMocks
|
||||||
private DefaultRoleService roleService;
|
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
|
@Nested
|
||||||
class ListDependenciesTest {
|
class ListDependenciesTest {
|
||||||
@Test
|
@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.junit.jupiter.api.Assertions.assertTrue;
|
||||||
import static org.mockito.ArgumentMatchers.any;
|
import static org.mockito.ArgumentMatchers.any;
|
||||||
import static org.mockito.Mockito.doNothing;
|
import static org.mockito.Mockito.doNothing;
|
||||||
import static org.mockito.Mockito.mock;
|
|
||||||
import static org.mockito.Mockito.never;
|
import static org.mockito.Mockito.never;
|
||||||
import static org.mockito.Mockito.times;
|
import static org.mockito.Mockito.times;
|
||||||
import static org.mockito.Mockito.verify;
|
import static org.mockito.Mockito.verify;
|
||||||
|
@ -175,9 +174,8 @@ class BackupReconcilerTest {
|
||||||
backup.getSpec().setFormat("zip");
|
backup.getSpec().setFormat("zip");
|
||||||
when(client.fetch(Backup.class, name)).thenReturn(Optional.of(backup));
|
when(client.fetch(Backup.class, name)).thenReturn(Optional.of(backup));
|
||||||
doNothing().when(client).update(backup);
|
doNothing().when(client).update(backup);
|
||||||
Mono<Void> mono = mock(Mono.class);
|
when(migrationService.backup(backup)).thenReturn(
|
||||||
when(mono.block()).thenThrow(Exceptions.propagate(new InterruptedException()));
|
Mono.error(Exceptions.propagate(new InterruptedException())));
|
||||||
when(migrationService.backup(backup)).thenReturn(mono);
|
|
||||||
|
|
||||||
var result = reconciler.reconcile(new Reconciler.Request(name));
|
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)).fetch(Backup.class, name);
|
||||||
verify(client, times(3)).update(backup);
|
verify(client, times(3)).update(backup);
|
||||||
verify(migrationService).backup(backup);
|
verify(migrationService).backup(backup);
|
||||||
verify(mono).block();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -206,9 +203,8 @@ class BackupReconcilerTest {
|
||||||
backup.getSpec().setFormat("zip");
|
backup.getSpec().setFormat("zip");
|
||||||
when(client.fetch(Backup.class, name)).thenReturn(Optional.of(backup));
|
when(client.fetch(Backup.class, name)).thenReturn(Optional.of(backup));
|
||||||
doNothing().when(client).update(backup);
|
doNothing().when(client).update(backup);
|
||||||
Mono<Void> mono = mock(Mono.class);
|
when(migrationService.backup(backup))
|
||||||
when(mono.block()).thenThrow(Exceptions.propagate(new IOException("File not found")));
|
.thenReturn(Mono.error(Exceptions.propagate(new IOException("File not found"))));
|
||||||
when(migrationService.backup(backup)).thenReturn(mono);
|
|
||||||
|
|
||||||
var result = reconciler.reconcile(new Reconciler.Request(name));
|
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)).fetch(Backup.class, name);
|
||||||
verify(client, times(3)).update(backup);
|
verify(client, times(3)).update(backup);
|
||||||
verify(migrationService).backup(backup);
|
verify(migrationService).backup(backup);
|
||||||
verify(mono).block();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@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;
|
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.ArgumentMatchers.eq;
|
||||||
import static org.mockito.Mockito.times;
|
import static org.mockito.Mockito.times;
|
||||||
import static org.mockito.Mockito.verify;
|
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.PUT;
|
||||||
import static org.springframework.web.reactive.function.server.RequestPredicates.accept;
|
import static org.springframework.web.reactive.function.server.RequestPredicates.accept;
|
||||||
import static org.springframework.web.reactive.function.server.RouterFunctions.route;
|
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.ArrayList;
|
||||||
import java.util.List;
|
|
||||||
import org.junit.jupiter.api.BeforeEach;
|
import org.junit.jupiter.api.BeforeEach;
|
||||||
import org.junit.jupiter.api.Disabled;
|
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.springframework.beans.factory.annotation.Autowired;
|
import org.springframework.beans.factory.annotation.Autowired;
|
||||||
import org.springframework.boot.test.autoconfigure.web.reactive.AutoConfigureWebTestClient;
|
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.boot.test.mock.mockito.MockBean;
|
||||||
import org.springframework.context.annotation.Bean;
|
import org.springframework.context.annotation.Bean;
|
||||||
import org.springframework.context.annotation.Import;
|
import org.springframework.context.annotation.Import;
|
||||||
import org.springframework.http.HttpHeaders;
|
|
||||||
import org.springframework.http.MediaType;
|
import org.springframework.http.MediaType;
|
||||||
import org.springframework.lang.NonNull;
|
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.ReactiveUserDetailsService;
|
||||||
import org.springframework.security.core.userdetails.User;
|
|
||||||
import org.springframework.security.test.context.support.WithMockUser;
|
import org.springframework.security.test.context.support.WithMockUser;
|
||||||
import org.springframework.test.web.reactive.server.WebTestClient;
|
import org.springframework.test.web.reactive.server.WebTestClient;
|
||||||
import org.springframework.web.reactive.function.server.RouterFunction;
|
import org.springframework.web.reactive.function.server.RouterFunction;
|
||||||
import org.springframework.web.reactive.function.server.ServerRequest;
|
import org.springframework.web.reactive.function.server.ServerRequest;
|
||||||
import org.springframework.web.reactive.function.server.ServerResponse;
|
import org.springframework.web.reactive.function.server.ServerResponse;
|
||||||
|
import reactor.core.publisher.Flux;
|
||||||
import reactor.core.publisher.Mono;
|
import reactor.core.publisher.Mono;
|
||||||
import run.halo.app.core.extension.Role;
|
import run.halo.app.core.extension.Role;
|
||||||
import run.halo.app.core.extension.Role.PolicyRule;
|
import run.halo.app.core.extension.Role.PolicyRule;
|
||||||
import run.halo.app.core.extension.service.RoleService;
|
import run.halo.app.core.extension.service.RoleService;
|
||||||
import run.halo.app.extension.Metadata;
|
import run.halo.app.extension.Metadata;
|
||||||
import run.halo.app.extension.exception.ExtensionNotFoundException;
|
|
||||||
import run.halo.app.infra.AnonymousUserConst;
|
import run.halo.app.infra.AnonymousUserConst;
|
||||||
import run.halo.app.security.LoginUtils;
|
|
||||||
|
|
||||||
@Disabled
|
|
||||||
@SpringBootTest
|
@SpringBootTest
|
||||||
@AutoConfigureWebTestClient
|
@AutoConfigureWebTestClient
|
||||||
@Import(AuthorizationTest.TestConfig.class)
|
@Import(AuthorizationTest.TestConfig.class)
|
||||||
|
@ -55,6 +49,9 @@ class AuthorizationTest {
|
||||||
@MockBean
|
@MockBean
|
||||||
ReactiveUserDetailsService userDetailsService;
|
ReactiveUserDetailsService userDetailsService;
|
||||||
|
|
||||||
|
@MockBean
|
||||||
|
ReactiveUserDetailsPasswordService userDetailsPasswordService;
|
||||||
|
|
||||||
@MockBean
|
@MockBean
|
||||||
RoleService roleService;
|
RoleService roleService;
|
||||||
|
|
||||||
|
@ -63,64 +60,16 @@ class AuthorizationTest {
|
||||||
webClient = webClient.mutateWith(csrf());
|
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
|
@Test
|
||||||
void anonymousUserAccessProtectedApi() {
|
void anonymousUserAccessProtectedApi() {
|
||||||
when(userDetailsService.findByUsername(eq(AnonymousUserConst.PRINCIPAL)))
|
when(userDetailsService.findByUsername(eq(AnonymousUserConst.PRINCIPAL)))
|
||||||
.thenReturn(Mono.empty());
|
.thenReturn(Mono.empty());
|
||||||
when(roleService.getMonoRole(AnonymousUserConst.Role))
|
when(roleService.listDependenciesFlux(anySet())).thenReturn(Flux.empty());
|
||||||
.thenReturn(Mono.empty());
|
|
||||||
webClient.get().uri("/apis/fake.halo.run/v1/posts").exchange().expectStatus()
|
webClient.get().uri("/apis/fake.halo.run/v1/posts").exchange().expectStatus()
|
||||||
.isUnauthorized();
|
.isUnauthorized();
|
||||||
|
|
||||||
verify(roleService, times(1)).getMonoRole(AnonymousUserConst.Role);
|
verify(roleService).listDependenciesFlux(anySet());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -137,25 +86,23 @@ class AuthorizationTest {
|
||||||
.resources("posts")
|
.resources("posts")
|
||||||
.build();
|
.build();
|
||||||
role.getRules().add(policyRule);
|
role.getRules().add(policyRule);
|
||||||
when(roleService.getMonoRole(AnonymousUserConst.Role))
|
when(roleService.listDependenciesFlux(anySet())).thenReturn(Flux.just(role));
|
||||||
.thenReturn(Mono.just(role));
|
|
||||||
webClient.get().uri("/apis/fake.halo.run/v1/posts").exchange().expectStatus()
|
webClient.get().uri("/apis/fake.halo.run/v1/posts").exchange().expectStatus()
|
||||||
.isOk()
|
.isOk()
|
||||||
.expectBody(String.class).isEqualTo("returned posts");
|
.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()
|
webClient.get().uri("/apis/fake.halo.run/v1/posts/hello-halo").exchange()
|
||||||
.expectStatus()
|
.expectStatus()
|
||||||
.isUnauthorized();
|
.isUnauthorized();
|
||||||
verify(roleService, times(2)).getMonoRole(AnonymousUserConst.Role);
|
|
||||||
|
verify(roleService, times(2)).listDependenciesFlux(anySet());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@WithMockUser(username = "user", roles = "post.read")
|
@WithMockUser(username = "user", roles = "post.read")
|
||||||
void authenticatedUserAccessAuthenticationFreeApi() {
|
void authenticatedUserAccessAuthenticationFreeApi() {
|
||||||
when(roleService.getMonoRole("authenticated")).thenReturn(Mono.empty());
|
|
||||||
when(roleService.getMonoRole("post.read")).thenReturn(Mono.empty());
|
|
||||||
Role role = new Role();
|
Role role = new Role();
|
||||||
role.setMetadata(new Metadata());
|
role.setMetadata(new Metadata());
|
||||||
role.getMetadata().setName(AnonymousUserConst.Role);
|
role.getMetadata().setName(AnonymousUserConst.Role);
|
||||||
|
@ -166,11 +113,13 @@ class AuthorizationTest {
|
||||||
.resources("posts")
|
.resources("posts")
|
||||||
.build();
|
.build();
|
||||||
role.getRules().add(policyRule);
|
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()
|
webClient.get().uri("/apis/fake.halo.run/v1/posts").exchange().expectStatus()
|
||||||
.isOk()
|
.isOk()
|
||||||
.expectBody(String.class).isEqualTo("returned posts");
|
.expectBody(String.class).isEqualTo("returned posts");
|
||||||
|
verify(roleService).listDependenciesFlux(anySet());
|
||||||
}
|
}
|
||||||
|
|
||||||
@TestConfiguration
|
@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.assertj.core.api.Assertions.assertThat;
|
||||||
import static org.junit.jupiter.api.Assertions.assertEquals;
|
import static org.junit.jupiter.api.Assertions.assertEquals;
|
||||||
import static org.junit.jupiter.api.Assertions.assertNotNull;
|
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 static org.springframework.mock.http.server.reactive.MockServerHttpRequest.method;
|
||||||
|
|
||||||
import java.util.Collection;
|
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
|
||||||
import org.junit.jupiter.api.Test;
|
import org.junit.jupiter.api.Test;
|
||||||
import org.springframework.http.HttpMethod;
|
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}.
|
* Tests for {@link RequestInfoFactory}.
|
||||||
*
|
*
|
||||||
* @author guqing
|
* @author guqing
|
||||||
* @see RbacRequestEvaluation
|
|
||||||
* @see RequestInfo
|
* @see RequestInfo
|
||||||
* @see DefaultRuleResolver
|
|
||||||
* @since 2.0.0
|
* @since 2.0.0
|
||||||
*/
|
*/
|
||||||
public class RequestInfoResolverTest {
|
public class RequestInfoResolverTest {
|
||||||
|
@ -173,7 +159,7 @@ public class RequestInfoResolverTest {
|
||||||
List.of(new ErrorCases("api resource has name and no subresource but post",
|
List.of(new ErrorCases("api resource has name and no subresource but post",
|
||||||
"/api/version/themes/install"),
|
"/api/version/themes/install"),
|
||||||
new ErrorCases("apis resource has name and no subresource but post",
|
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) {
|
for (ErrorCases errorCase : postCases) {
|
||||||
var request =
|
var request =
|
||||||
method(HttpMethod.POST, errorCase.url).build();
|
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 NonApiCase(String url, boolean expected) {
|
||||||
}
|
}
|
||||||
|
|
||||||
public record ErrorCases(String desc, String url) {
|
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,
|
public record SuccessCase(String method, String url, String expectedVerb,
|
||||||
String expectedAPIPrefix, String expectedAPIGroup,
|
String expectedAPIPrefix, String expectedAPIGroup,
|
||||||
|
|
Loading…
Reference in New Issue