diff --git a/src/main/java/run/halo/app/config/WebSecurityConfig.java b/src/main/java/run/halo/app/config/WebSecurityConfig.java index 015fec17f..b23e3e4a9 100644 --- a/src/main/java/run/halo/app/config/WebSecurityConfig.java +++ b/src/main/java/run/halo/app/config/WebSecurityConfig.java @@ -53,10 +53,8 @@ import run.halo.app.identity.authentication.ProviderSettings; import run.halo.app.identity.authentication.verifier.BearerTokenAuthenticationFilter; import run.halo.app.identity.authentication.verifier.JwtAccessTokenNonBlockedValidator; import run.halo.app.identity.authentication.verifier.TokenAuthenticationManagerResolver; -import run.halo.app.identity.authorization.DefaultRoleBindingLister; import run.halo.app.identity.authorization.DefaultRoleGetter; import run.halo.app.identity.authorization.RequestInfoAuthorizationManager; -import run.halo.app.identity.authorization.RoleBindingLister; import run.halo.app.identity.authorization.RoleGetter; import run.halo.app.identity.entrypoint.JwtAccessDeniedHandler; import run.halo.app.identity.entrypoint.JwtAuthenticationEntryPoint; @@ -131,9 +129,8 @@ public class WebSecurityConfig { } RequestInfoAuthorizationManager requestInfoAuthorizationManager() { - RoleBindingLister roleBindingLister = new DefaultRoleBindingLister(); RoleGetter roleGetter = new DefaultRoleGetter(extensionClient); - return new RequestInfoAuthorizationManager(roleGetter, roleBindingLister); + return new RequestInfoAuthorizationManager(roleGetter); } AuthenticationManagerResolver authenticationManagerResolver() { diff --git a/src/main/java/run/halo/app/identity/authorization/DefaultRoleBindingLister.java b/src/main/java/run/halo/app/identity/authorization/DefaultRoleBindingLister.java index 66f3b21c6..7c03e9313 100644 --- a/src/main/java/run/halo/app/identity/authorization/DefaultRoleBindingLister.java +++ b/src/main/java/run/halo/app/identity/authorization/DefaultRoleBindingLister.java @@ -1,12 +1,13 @@ package run.halo.app.identity.authorization; -import java.util.ArrayList; import java.util.Collections; -import java.util.List; +import java.util.Set; +import java.util.stream.Collectors; +import lombok.extern.slf4j.Slf4j; import org.springframework.security.core.Authentication; import org.springframework.security.core.GrantedAuthority; import org.springframework.security.core.context.SecurityContextHolder; -import run.halo.app.extension.Metadata; +import org.springframework.security.web.authentication.AnonymousAuthenticationFilter; /** *

Obtain the authorities from the authenticated authentication and construct it as a RoleBinding @@ -15,67 +16,29 @@ import run.halo.app.extension.Metadata; * so there is no need to query from the database.

*

For tokens in other formats, after authentication, fill the authorities with the token into * the SecurityContextHolder.

- *
- * kind: RoleBinding
- * metadata:
- *   name: some-name
- * subjects:
- * # You can specify more than one "subject"
- * - kind: User
- *   name: some-username
- * roleRef:
- *   kind: Role
- *   name: role-name
- * 
* * @author guqing + * @see AnonymousAuthenticationFilter * @since 2.0.0 */ +@Slf4j public class DefaultRoleBindingLister implements RoleBindingLister { private static final String SCOPE_AUTHORITY_PREFIX = "SCOPE_"; private static final String ROLE_AUTHORITY_PREFIX = "ROLE_"; @Override - public List listRoleBindings() { + public Set listBoundRoleNames() { Authentication authentication = SecurityContextHolder.getContext() .getAuthentication(); if (authentication == null) { - return Collections.emptyList(); + log.debug("No authentication found in SecurityContext."); + return Collections.emptySet(); } - String username = authentication.getName(); - - List roleNames = roleNamesFromAuthentication(); - - List roleBindings = new ArrayList<>(roleNames.size()); - for (String roleName : roleNames) { - RoleBinding roleBinding = new RoleBinding(); - // metadata - Metadata metadata = new Metadata(); - metadata.setName(username + "_" + roleName); - roleBinding.setMetadata(metadata); - - // role ref - RoleRef roleRef = new RoleRef(); - roleRef.setKind("Role"); - roleRef.setName(roleName); - roleBinding.setRoleRef(roleRef); - - // subject - Subject subject = new Subject(); - subject.setKind("User"); - subject.setName(username); - roleBinding.setSubjects(List.of(subject)); - roleBindings.add(roleBinding); - } - return roleBindings; - } - - private List roleNamesFromAuthentication() { - Authentication authentication = SecurityContextHolder.getContext() - .getAuthentication(); return authentication.getAuthorities() .stream() .map(GrantedAuthority::getAuthority) + // Exclude anonymous user roles + .filter(authority -> !authority.equals("ROLE_ANONYMOUS")) .map(scope -> { if (scope.startsWith(SCOPE_AUTHORITY_PREFIX)) { return scope.replaceFirst(SCOPE_AUTHORITY_PREFIX, ""); @@ -85,6 +48,6 @@ public class DefaultRoleBindingLister implements RoleBindingLister { } return scope; }) - .toList(); + .collect(Collectors.toSet()); } } diff --git a/src/main/java/run/halo/app/identity/authorization/DefaultRuleResolver.java b/src/main/java/run/halo/app/identity/authorization/DefaultRuleResolver.java index ca6d09650..178586d7a 100644 --- a/src/main/java/run/halo/app/identity/authorization/DefaultRuleResolver.java +++ b/src/main/java/run/halo/app/identity/authorization/DefaultRuleResolver.java @@ -2,9 +2,10 @@ package run.halo.app.identity.authorization; import java.util.Collections; import java.util.List; +import java.util.Set; import lombok.Data; -import org.apache.commons.lang3.StringUtils; import org.springframework.security.core.userdetails.UserDetails; +import org.springframework.util.Assert; /** * @author guqing @@ -12,13 +13,13 @@ import org.springframework.security.core.userdetails.UserDetails; */ @Data public class DefaultRuleResolver implements AuthorizationRuleResolver { - private static final String USER_KIND = "User"; - RoleGetter roleGetter; - RoleBindingLister roleBindingLister; - public DefaultRuleResolver(RoleGetter roleGetter, RoleBindingLister roleBindingLister) { + private RoleGetter roleGetter; + + private RoleBindingLister roleBindingLister = new DefaultRoleBindingLister(); + + public DefaultRuleResolver(RoleGetter roleGetter) { this.roleGetter = roleGetter; - this.roleBindingLister = roleBindingLister; } @Override @@ -38,26 +39,12 @@ public class DefaultRuleResolver implements AuthorizationRuleResolver { @Override public void visitRulesFor(UserDetails user, RuleAccumulator visitor) { - List roleBindings = Collections.emptyList(); - try { - roleBindings = roleBindingLister.listRoleBindings(); - } catch (Exception e) { - if (visitor.visit(null, null, e)) { - return; - } - } + Set roleNames = roleBindingLister.listBoundRoleNames(); - for (RoleBinding roleBinding : roleBindings) { - AppliesResult appliesResult = appliesTo(user, roleBinding.subjects); - if (!appliesResult.applies) { - continue; - } - - Subject subject = roleBinding.subjects.get(appliesResult.subjectIndex); - - List rules = Collections.emptyList(); + List rules = Collections.emptyList(); + for (String roleName : roleNames) { try { - Role role = roleGetter.getRole(roleBinding.roleRef.name); + Role role = roleGetter.getRole(roleName); rules = role.getRules(); } catch (Exception e) { if (visitor.visit(null, null, e)) { @@ -65,7 +52,7 @@ public class DefaultRuleResolver implements AuthorizationRuleResolver { } } - String source = roleBindingDescriber(roleBinding, subject); + String source = roleBindingDescriber(roleName, user.getUsername()); for (PolicyRule rule : rules) { if (!visitor.visit(source, rule, null)) { return; @@ -74,28 +61,12 @@ public class DefaultRuleResolver implements AuthorizationRuleResolver { } } - String roleBindingDescriber(RoleBinding roleBinding, Subject subject) { - String describeSubject = String.format("%s %s", subject.kind, subject.name); - return String.format("RoleBinding %s of %s %s to %s", roleBinding.getMetadata().getName(), - roleBinding.roleRef.getKind(), roleBinding.roleRef.getName(), describeSubject); + String roleBindingDescriber(String roleName, String subject) { + return String.format("Binding role [%s] to [%s]", roleName, subject); } - AppliesResult appliesTo(UserDetails user, List bindingSubjects) { - for (int i = 0; i < bindingSubjects.size(); i++) { - if (appliesToUser(user, bindingSubjects.get(i))) { - return new AppliesResult(true, i); - } - } - return new AppliesResult(false, 0); - } - - boolean appliesToUser(UserDetails user, Subject subject) { - if (USER_KIND.equals(subject.kind)) { - return StringUtils.equals(user.getUsername(), subject.name); - } - return false; - } - - record AppliesResult(boolean applies, int subjectIndex) { + public void setRoleBindingLister(RoleBindingLister roleBindingLister) { + Assert.notNull(roleBindingLister, "The roleBindingLister must not be null."); + this.roleBindingLister = roleBindingLister; } } diff --git a/src/main/java/run/halo/app/identity/authorization/NonResourceRuleInfo.java b/src/main/java/run/halo/app/identity/authorization/NonResourceRuleInfo.java deleted file mode 100644 index ee3fb590b..000000000 --- a/src/main/java/run/halo/app/identity/authorization/NonResourceRuleInfo.java +++ /dev/null @@ -1,8 +0,0 @@ -package run.halo.app.identity.authorization; - -/** - * @author guqing - * @since 2.0.0 - */ -public class NonResourceRuleInfo { -} diff --git a/src/main/java/run/halo/app/identity/authorization/PolicyRuleInfo.java b/src/main/java/run/halo/app/identity/authorization/PolicyRuleInfo.java deleted file mode 100644 index 278ebab12..000000000 --- a/src/main/java/run/halo/app/identity/authorization/PolicyRuleInfo.java +++ /dev/null @@ -1,12 +0,0 @@ -package run.halo.app.identity.authorization; - -import java.util.List; - -/** - * @author guqing - * @since 2.0.0 - */ -public class PolicyRuleInfo { - List resourceRules; - List nonResourceRules; -} diff --git a/src/main/java/run/halo/app/identity/authorization/RequestInfoAuthorizationManager.java b/src/main/java/run/halo/app/identity/authorization/RequestInfoAuthorizationManager.java index 7cd39fff2..eb655e97a 100644 --- a/src/main/java/run/halo/app/identity/authorization/RequestInfoAuthorizationManager.java +++ b/src/main/java/run/halo/app/identity/authorization/RequestInfoAuthorizationManager.java @@ -26,9 +26,8 @@ public class RequestInfoAuthorizationManager private AuthorizationRuleResolver ruleResolver; - public RequestInfoAuthorizationManager(RoleGetter roleGetter, - RoleBindingLister roleBindingLister) { - this.ruleResolver = new DefaultRuleResolver(roleGetter, roleBindingLister); + public RequestInfoAuthorizationManager(RoleGetter roleGetter) { + this.ruleResolver = new DefaultRuleResolver(roleGetter); } @Override diff --git a/src/main/java/run/halo/app/identity/authorization/ResourceRuleInfo.java b/src/main/java/run/halo/app/identity/authorization/ResourceRuleInfo.java deleted file mode 100644 index 31b1b372c..000000000 --- a/src/main/java/run/halo/app/identity/authorization/ResourceRuleInfo.java +++ /dev/null @@ -1,8 +0,0 @@ -package run.halo.app.identity.authorization; - -/** - * @author guqing - * @since 2.0.0 - */ -public class ResourceRuleInfo { -} diff --git a/src/main/java/run/halo/app/identity/authorization/RoleBindingLister.java b/src/main/java/run/halo/app/identity/authorization/RoleBindingLister.java index 8c67502ef..8c1352734 100644 --- a/src/main/java/run/halo/app/identity/authorization/RoleBindingLister.java +++ b/src/main/java/run/halo/app/identity/authorization/RoleBindingLister.java @@ -1,6 +1,6 @@ package run.halo.app.identity.authorization; -import java.util.List; +import java.util.Set; /** * @author guqing @@ -9,5 +9,5 @@ import java.util.List; @FunctionalInterface public interface RoleBindingLister { - List listRoleBindings(); + Set listBoundRoleNames(); } diff --git a/src/test/java/run/halo/app/authorization/DefaultRoleBindingListerTest.java b/src/test/java/run/halo/app/authorization/DefaultRoleBindingListerTest.java index 8215bb643..fbb3f9e86 100644 --- a/src/test/java/run/halo/app/authorization/DefaultRoleBindingListerTest.java +++ b/src/test/java/run/halo/app/authorization/DefaultRoleBindingListerTest.java @@ -2,14 +2,13 @@ package run.halo.app.authorization; import static org.assertj.core.api.Assertions.assertThat; -import java.util.List; +import java.util.Set; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Test; import org.junit.jupiter.api.extension.ExtendWith; import org.springframework.security.test.context.support.WithMockUser; import org.springframework.test.context.junit.jupiter.SpringExtension; import run.halo.app.identity.authorization.DefaultRoleBindingLister; -import run.halo.app.identity.authorization.RoleBinding; /** * Tests for {@link DefaultRoleBindingLister}. @@ -30,30 +29,15 @@ public class DefaultRoleBindingListerTest { @Test @WithMockUser(username = "test", roles = {"readPost", "readTag"}) void listWhenAuthorizedWithTwoRoles() { - List roleBindings = roleBindingLister.listRoleBindings(); + Set roleBindings = roleBindingLister.listBoundRoleNames(); assertThat(roleBindings).isNotNull(); assertThat(roleBindings.size()).isEqualTo(2); - - RoleBinding readPostRoleBinding = roleBindings.get(0); - assertThat(readPostRoleBinding.getMetadata()).isNotNull(); - assertThat(readPostRoleBinding.getMetadata().getName()).isNotNull(); - assertThat(readPostRoleBinding.getSubjects()).allMatch(subject -> - "test".equals(subject.getName()) - && "User".equals(subject.getKind())); - assertThat(readPostRoleBinding.getRoleRef().getName()).isEqualTo("readPost"); - - RoleBinding readTagRoleBinding = roleBindings.get(1); - assertThat(readTagRoleBinding.getMetadata()).isNotNull(); - assertThat(readTagRoleBinding.getMetadata().getName()).isNotNull(); - assertThat(readTagRoleBinding.getSubjects()).allMatch(subject -> - "test".equals(subject.getName()) - && "User".equals(subject.getKind())); - assertThat(readTagRoleBinding.getRoleRef().getName()).isEqualTo("readTag"); + assertThat(roleBindings).containsAll(Set.of("readPost", "readTag")); } @Test void listWhenUnauthorizedThenEmpty() { - List roleBindings = roleBindingLister.listRoleBindings(); + Set roleBindings = roleBindingLister.listBoundRoleNames(); assertThat(roleBindings).isEmpty(); } } diff --git a/src/test/java/run/halo/app/authorization/RequestInfoResolverTest.java b/src/test/java/run/halo/app/authorization/RequestInfoResolverTest.java index fb388d296..0a8e16377 100644 --- a/src/test/java/run/halo/app/authorization/RequestInfoResolverTest.java +++ b/src/test/java/run/halo/app/authorization/RequestInfoResolverTest.java @@ -5,6 +5,7 @@ import static org.assertj.core.api.Assertions.assertThat; import java.util.HashMap; import java.util.List; import java.util.Map; +import java.util.Set; import org.apache.commons.lang3.RegExUtils; import org.junit.jupiter.api.Test; import org.springframework.mock.web.MockHttpServletRequest; @@ -21,9 +22,6 @@ import run.halo.app.identity.authorization.RbacRequestEvaluation; import run.halo.app.identity.authorization.RequestInfo; import run.halo.app.identity.authorization.RequestInfoFactory; import run.halo.app.identity.authorization.Role; -import run.halo.app.identity.authorization.RoleBinding; -import run.halo.app.identity.authorization.RoleRef; -import run.halo.app.identity.authorization.Subject; /** * Tests for {@link RequestInfoFactory}. @@ -135,29 +133,12 @@ public class RequestInfoResolverTest { metadata.setName("ruleReadPost"); role.setMetadata(metadata); return role; - }, () -> { - // role binding lister - RoleBinding roleBinding = new RoleBinding(); - Metadata metadata = new Metadata(); - metadata.setName("admin_ruleReadPost"); - roleBinding.setMetadata(metadata); - - Subject subject = new Subject(); - subject.setName("admin"); - subject.setKind("User"); - subject.setApiGroup(""); - roleBinding.setSubjects(List.of(subject)); - - RoleRef roleRef = new RoleRef(); - roleRef.setKind("Role"); - roleRef.setName("ruleReadPost"); - roleRef.setApiGroup(""); - roleBinding.setRoleRef(roleRef); - - return List.of(roleBinding); }); + // list bound role names + ruleResolver.setRoleBindingLister(() -> Set.of("ruleReadPost")); User user = new User("admin", "123456", AuthorityUtils.createAuthorityList("ruleReadPost")); + // resolve user rules List rules = ruleResolver.rulesFor(user); assertThat(rules).isNotNull(); @@ -175,7 +156,6 @@ public class RequestInfoResolverTest { } } - public record NonApiCase(String url, boolean expected){} public record ErrorCases(String desc, String url) {} diff --git a/src/test/java/run/halo/app/integration/security/TestWebSecurityConfig.java b/src/test/java/run/halo/app/integration/security/TestWebSecurityConfig.java index b48d40c97..d543abc00 100644 --- a/src/test/java/run/halo/app/integration/security/TestWebSecurityConfig.java +++ b/src/test/java/run/halo/app/integration/security/TestWebSecurityConfig.java @@ -59,9 +59,6 @@ import run.halo.app.identity.authentication.verifier.TokenAuthenticationManagerR import run.halo.app.identity.authorization.PolicyRule; import run.halo.app.identity.authorization.RequestInfoAuthorizationManager; import run.halo.app.identity.authorization.Role; -import run.halo.app.identity.authorization.RoleBinding; -import run.halo.app.identity.authorization.RoleRef; -import run.halo.app.identity.authorization.Subject; import run.halo.app.identity.entrypoint.Oauth2LogoutHandler; import run.halo.app.infra.properties.JwtProperties; import run.halo.app.infra.utils.HaloUtils; @@ -140,27 +137,6 @@ public class TestWebSecurityConfig { metadata.setName("ruleReadPost"); role.setMetadata(metadata); return role; - }, () -> { - // role binding lister - RoleBinding roleBinding = new RoleBinding(); - - Metadata metadata = new Metadata(); - metadata.setName("userRoleBinding"); - roleBinding.setMetadata(metadata); - - Subject subject = new Subject(); - subject.setName("test_user"); - subject.setKind("User"); - subject.setApiGroup(""); - roleBinding.setSubjects(List.of(subject)); - - RoleRef roleRef = new RoleRef(); - roleRef.setKind("Role"); - roleRef.setName("ruleReadPost"); - roleRef.setApiGroup(""); - roleBinding.setRoleRef(roleRef); - - return List.of(roleBinding); }); }