mirror of https://github.com/halo-dev/halo
feat: add default role binding lister and role getter (#2108)
* feat: add default role binding lister and role getter * feat: add initial capacity for role binding listpull/2110/head
parent
9990fdd086
commit
c5df6d3dbb
|
@ -10,7 +10,6 @@ import jakarta.servlet.http.HttpServletRequest;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.security.interfaces.RSAPrivateKey;
|
import java.security.interfaces.RSAPrivateKey;
|
||||||
import java.security.interfaces.RSAPublicKey;
|
import java.security.interfaces.RSAPublicKey;
|
||||||
import java.util.List;
|
|
||||||
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
import org.springframework.boot.context.properties.EnableConfigurationProperties;
|
||||||
import org.springframework.context.annotation.Bean;
|
import org.springframework.context.annotation.Bean;
|
||||||
import org.springframework.security.authentication.AuthenticationManager;
|
import org.springframework.security.authentication.AuthenticationManager;
|
||||||
|
@ -33,7 +32,8 @@ import org.springframework.security.provisioning.InMemoryUserDetailsManager;
|
||||||
import org.springframework.security.web.SecurityFilterChain;
|
import org.springframework.security.web.SecurityFilterChain;
|
||||||
import org.springframework.security.web.access.intercept.FilterSecurityInterceptor;
|
import org.springframework.security.web.access.intercept.FilterSecurityInterceptor;
|
||||||
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;
|
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;
|
||||||
import org.springframework.security.web.context.SecurityContextPersistenceFilter;
|
import org.springframework.security.web.context.SecurityContextHolderFilter;
|
||||||
|
import run.halo.app.extension.ExtensionClient;
|
||||||
import run.halo.app.identity.authentication.InMemoryOAuth2AuthorizationService;
|
import run.halo.app.identity.authentication.InMemoryOAuth2AuthorizationService;
|
||||||
import run.halo.app.identity.authentication.JwtGenerator;
|
import run.halo.app.identity.authentication.JwtGenerator;
|
||||||
import run.halo.app.identity.authentication.OAuth2AuthorizationService;
|
import run.halo.app.identity.authentication.OAuth2AuthorizationService;
|
||||||
|
@ -44,16 +44,14 @@ import run.halo.app.identity.authentication.ProviderContextFilter;
|
||||||
import run.halo.app.identity.authentication.ProviderSettings;
|
import run.halo.app.identity.authentication.ProviderSettings;
|
||||||
import run.halo.app.identity.authentication.verifier.BearerTokenAuthenticationFilter;
|
import run.halo.app.identity.authentication.verifier.BearerTokenAuthenticationFilter;
|
||||||
import run.halo.app.identity.authentication.verifier.JwtProvidedDecoderAuthenticationManagerResolver;
|
import run.halo.app.identity.authentication.verifier.JwtProvidedDecoderAuthenticationManagerResolver;
|
||||||
import run.halo.app.identity.authorization.PolicyRule;
|
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.RequestInfoAuthorizationManager;
|
||||||
import run.halo.app.identity.authorization.Role;
|
import run.halo.app.identity.authorization.RoleBindingLister;
|
||||||
import run.halo.app.identity.authorization.RoleBinding;
|
import run.halo.app.identity.authorization.RoleGetter;
|
||||||
import run.halo.app.identity.authorization.RoleRef;
|
|
||||||
import run.halo.app.identity.authorization.Subject;
|
|
||||||
import run.halo.app.identity.entrypoint.JwtAccessDeniedHandler;
|
import run.halo.app.identity.entrypoint.JwtAccessDeniedHandler;
|
||||||
import run.halo.app.identity.entrypoint.JwtAuthenticationEntryPoint;
|
import run.halo.app.identity.entrypoint.JwtAuthenticationEntryPoint;
|
||||||
import run.halo.app.infra.properties.JwtProperties;
|
import run.halo.app.infra.properties.JwtProperties;
|
||||||
import run.halo.app.infra.types.ObjectMeta;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author guqing
|
* @author guqing
|
||||||
|
@ -69,11 +67,15 @@ public class WebSecurityConfig {
|
||||||
|
|
||||||
private final AuthenticationManagerBuilder authenticationManagerBuilder;
|
private final AuthenticationManagerBuilder authenticationManagerBuilder;
|
||||||
|
|
||||||
|
private final ExtensionClient extensionClient;
|
||||||
|
|
||||||
public WebSecurityConfig(JwtProperties jwtProperties,
|
public WebSecurityConfig(JwtProperties jwtProperties,
|
||||||
AuthenticationManagerBuilder authenticationManagerBuilder) throws IOException {
|
AuthenticationManagerBuilder authenticationManagerBuilder,
|
||||||
|
ExtensionClient extensionClient) throws IOException {
|
||||||
this.key = jwtProperties.readPublicKey();
|
this.key = jwtProperties.readPublicKey();
|
||||||
this.priv = jwtProperties.readPrivateKey();
|
this.priv = jwtProperties.readPrivateKey();
|
||||||
this.authenticationManagerBuilder = authenticationManagerBuilder;
|
this.authenticationManagerBuilder = authenticationManagerBuilder;
|
||||||
|
this.extensionClient = extensionClient;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Bean
|
@Bean
|
||||||
|
@ -97,7 +99,7 @@ public class WebSecurityConfig {
|
||||||
FilterSecurityInterceptor.class)
|
FilterSecurityInterceptor.class)
|
||||||
.addFilterBefore(new BearerTokenAuthenticationFilter(authenticationManagerResolver()),
|
.addFilterBefore(new BearerTokenAuthenticationFilter(authenticationManagerResolver()),
|
||||||
BasicAuthenticationFilter.class)
|
BasicAuthenticationFilter.class)
|
||||||
.addFilterAfter(providerContextFilter, SecurityContextPersistenceFilter.class)
|
.addFilterAfter(providerContextFilter, SecurityContextHolderFilter.class)
|
||||||
.sessionManagement(
|
.sessionManagement(
|
||||||
(session) -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
|
(session) -> session.sessionCreationPolicy(SessionCreationPolicy.STATELESS))
|
||||||
.exceptionHandling((exceptions) -> exceptions
|
.exceptionHandling((exceptions) -> exceptions
|
||||||
|
@ -107,47 +109,10 @@ public class WebSecurityConfig {
|
||||||
return http.build();
|
return http.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
public RequestInfoAuthorizationManager requestInfoAuthorizationManager() {
|
RequestInfoAuthorizationManager requestInfoAuthorizationManager() {
|
||||||
// TODO fake role and role bindings, only used for testing/development
|
RoleBindingLister roleBindingLister = new DefaultRoleBindingLister();
|
||||||
// It'll be deleted next time
|
RoleGetter roleGetter = new DefaultRoleGetter(extensionClient);
|
||||||
return new RequestInfoAuthorizationManager(name -> {
|
return new RequestInfoAuthorizationManager(roleGetter, roleBindingLister);
|
||||||
// role getter
|
|
||||||
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().nonResourceURLs("/healthy").verbs("get", "post", "head")
|
|
||||||
.build()
|
|
||||||
);
|
|
||||||
role.setRules(rules);
|
|
||||||
ObjectMeta objectMeta = new ObjectMeta();
|
|
||||||
objectMeta.setName("ruleReadPost");
|
|
||||||
role.setObjectMeta(objectMeta);
|
|
||||||
return role;
|
|
||||||
}, () -> {
|
|
||||||
// role binding lister
|
|
||||||
RoleBinding roleBinding = new RoleBinding();
|
|
||||||
|
|
||||||
ObjectMeta objectMeta = new ObjectMeta();
|
|
||||||
objectMeta.setName("userRoleBinding");
|
|
||||||
roleBinding.setObjectMeta(objectMeta);
|
|
||||||
|
|
||||||
Subject subject = new Subject();
|
|
||||||
subject.setName("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);
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
AuthenticationManagerResolver<HttpServletRequest> authenticationManagerResolver() {
|
AuthenticationManagerResolver<HttpServletRequest> authenticationManagerResolver() {
|
||||||
|
|
|
@ -0,0 +1,90 @@
|
||||||
|
package run.halo.app.identity.authorization;
|
||||||
|
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
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;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* <p>Obtain the authorities from the authenticated authentication and construct it as a RoleBinding
|
||||||
|
* list.</p>
|
||||||
|
* <p>After JWT authentication, the roles stored in the authorities are the roles owned by the user,
|
||||||
|
* so there is no need to query from the database.</p>
|
||||||
|
* <p>For tokens in other formats, after authentication, fill the authorities with the token into
|
||||||
|
* the SecurityContextHolder.</p>
|
||||||
|
* <pre>
|
||||||
|
* 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
|
||||||
|
* </pre>
|
||||||
|
*
|
||||||
|
* @author guqing
|
||||||
|
* @since 2.0.0
|
||||||
|
*/
|
||||||
|
public class DefaultRoleBindingLister implements RoleBindingLister {
|
||||||
|
private static final String SCOPE_AUTHORITY_PREFIX = "SCOPE_";
|
||||||
|
private static final String ROLE_AUTHORITY_PREFIX = "ROLE_";
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public List<RoleBinding> listRoleBindings() {
|
||||||
|
Authentication authentication = SecurityContextHolder.getContext()
|
||||||
|
.getAuthentication();
|
||||||
|
if (authentication == null) {
|
||||||
|
return Collections.emptyList();
|
||||||
|
}
|
||||||
|
String username = authentication.getName();
|
||||||
|
|
||||||
|
List<String> roleNames = roleNamesFromAuthentication();
|
||||||
|
|
||||||
|
List<RoleBinding> 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<String> roleNamesFromAuthentication() {
|
||||||
|
Authentication authentication = SecurityContextHolder.getContext()
|
||||||
|
.getAuthentication();
|
||||||
|
return authentication.getAuthorities()
|
||||||
|
.stream()
|
||||||
|
.map(GrantedAuthority::getAuthority)
|
||||||
|
.map(scope -> {
|
||||||
|
if (scope.startsWith(SCOPE_AUTHORITY_PREFIX)) {
|
||||||
|
return scope.replaceFirst(SCOPE_AUTHORITY_PREFIX, "");
|
||||||
|
}
|
||||||
|
if (scope.startsWith(ROLE_AUTHORITY_PREFIX)) {
|
||||||
|
return scope.replaceFirst(ROLE_AUTHORITY_PREFIX, "");
|
||||||
|
}
|
||||||
|
return scope;
|
||||||
|
})
|
||||||
|
.toList();
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,25 @@
|
||||||
|
package run.halo.app.identity.authorization;
|
||||||
|
|
||||||
|
import org.springframework.lang.NonNull;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
import run.halo.app.extension.ExtensionClient;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author guqing
|
||||||
|
* @since 2.0.0
|
||||||
|
*/
|
||||||
|
@Component
|
||||||
|
public class DefaultRoleGetter implements RoleGetter {
|
||||||
|
|
||||||
|
private final ExtensionClient extensionClient;
|
||||||
|
|
||||||
|
public DefaultRoleGetter(ExtensionClient extensionClient) {
|
||||||
|
this.extensionClient = extensionClient;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@NonNull
|
||||||
|
public Role getRole(@NonNull String name) {
|
||||||
|
return extensionClient.fetch(Role.class, name).orElseThrow();
|
||||||
|
}
|
||||||
|
}
|
|
@ -76,7 +76,7 @@ public class DefaultRuleResolver implements AuthorizationRuleResolver {
|
||||||
|
|
||||||
String roleBindingDescriber(RoleBinding roleBinding, Subject subject) {
|
String roleBindingDescriber(RoleBinding roleBinding, Subject subject) {
|
||||||
String describeSubject = String.format("%s %s", subject.kind, subject.name);
|
String describeSubject = String.format("%s %s", subject.kind, subject.name);
|
||||||
return String.format("RoleBinding %s of %s %s to %s", roleBinding.getName(),
|
return String.format("RoleBinding %s of %s %s to %s", roleBinding.metadata().getName(),
|
||||||
roleBinding.roleRef.getKind(), roleBinding.roleRef.getName(), describeSubject);
|
roleBinding.roleRef.getKind(), roleBinding.roleRef.getName(), describeSubject);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -6,7 +6,7 @@ import lombok.NoArgsConstructor;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* PolicyRule holds information that describes a policy rule, but does not contain information
|
* PolicyRule holds information that describes a policy rule, but does not contain information
|
||||||
* about who the rule applies to or which namespace the rule applies to.
|
* about whom the rule applies to or which namespace the rule applies to.
|
||||||
*
|
*
|
||||||
* @author guqing
|
* @author guqing
|
||||||
* @since 2.0.0
|
* @since 2.0.0
|
||||||
|
|
|
@ -1,20 +1,23 @@
|
||||||
package run.halo.app.identity.authorization;
|
package run.halo.app.identity.authorization;
|
||||||
|
|
||||||
|
import io.swagger.v3.oas.annotations.media.Schema;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
import run.halo.app.infra.types.ObjectMeta;
|
import lombok.EqualsAndHashCode;
|
||||||
import run.halo.app.infra.types.TypeMeta;
|
import lombok.ToString;
|
||||||
|
import run.halo.app.extension.AbstractExtension;
|
||||||
|
import run.halo.app.extension.GVK;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author guqing
|
* @author guqing
|
||||||
* @since 2.0.0
|
* @since 2.0.0
|
||||||
*/
|
*/
|
||||||
@Data
|
@Data
|
||||||
public class Role {
|
@EqualsAndHashCode(callSuper = true)
|
||||||
|
@ToString(callSuper = true)
|
||||||
TypeMeta typeMeta;
|
@GVK(group = "", version = "v1alpha1", kind = "Role", plural = "roles", singular = "role")
|
||||||
|
public class Role extends AbstractExtension {
|
||||||
ObjectMeta objectMeta;
|
|
||||||
|
|
||||||
|
@Schema(minLength = 1)
|
||||||
List<PolicyRule> rules;
|
List<PolicyRule> rules;
|
||||||
}
|
}
|
||||||
|
|
|
@ -2,26 +2,25 @@ package run.halo.app.identity.authorization;
|
||||||
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import lombok.Data;
|
import lombok.Data;
|
||||||
import org.apache.commons.lang3.StringUtils;
|
import lombok.EqualsAndHashCode;
|
||||||
import run.halo.app.infra.types.ObjectMeta;
|
import lombok.ToString;
|
||||||
import run.halo.app.infra.types.TypeMeta;
|
import run.halo.app.extension.AbstractExtension;
|
||||||
|
import run.halo.app.extension.GVK;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* // RoleBinding references a role, but does not contain it. It can reference a Role in the
|
* RoleBinding references a role, but does not contain it.
|
||||||
* same namespace or a ClusterRole in the global namespace.
|
* It can reference a Role in the global.
|
||||||
* // It adds who information via Subjects and namespace information by which namespace it exists
|
* It adds who information via Subjects.
|
||||||
* in. RoleBindings in a given
|
|
||||||
* // namespace only have effect in that namespace.
|
|
||||||
*
|
*
|
||||||
* @author guqing
|
* @author guqing
|
||||||
* @since 2.0.0
|
* @since 2.0.0
|
||||||
*/
|
*/
|
||||||
@Data
|
@Data
|
||||||
public class RoleBinding {
|
@EqualsAndHashCode(callSuper = true)
|
||||||
|
@ToString(callSuper = true)
|
||||||
TypeMeta typeMeta;
|
@GVK(group = "", version = "v1alpha1", kind = "RoleBinding", plural = "rolebindings",
|
||||||
|
singular = "rolebinding")
|
||||||
ObjectMeta objectMeta;
|
public class RoleBinding extends AbstractExtension {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Subjects holds references to the objects the role applies to.
|
* Subjects holds references to the objects the role applies to.
|
||||||
|
@ -34,11 +33,4 @@ public class RoleBinding {
|
||||||
* If the RoleRef cannot be resolved, the Authorizer must return an error.
|
* If the RoleRef cannot be resolved, the Authorizer must return an error.
|
||||||
*/
|
*/
|
||||||
RoleRef roleRef;
|
RoleRef roleRef;
|
||||||
|
|
||||||
public String getName() {
|
|
||||||
if (objectMeta == null) {
|
|
||||||
return StringUtils.EMPTY;
|
|
||||||
}
|
|
||||||
return objectMeta.getName();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -8,5 +8,6 @@ import java.util.List;
|
||||||
*/
|
*/
|
||||||
@FunctionalInterface
|
@FunctionalInterface
|
||||||
public interface RoleBindingLister {
|
public interface RoleBindingLister {
|
||||||
|
|
||||||
List<RoleBinding> listRoleBindings();
|
List<RoleBinding> listRoleBindings();
|
||||||
}
|
}
|
||||||
|
|
|
@ -0,0 +1,19 @@
|
||||||
|
package run.halo.app.infra;
|
||||||
|
|
||||||
|
import org.springframework.boot.context.event.ApplicationStartedEvent;
|
||||||
|
import org.springframework.context.ApplicationListener;
|
||||||
|
import org.springframework.stereotype.Component;
|
||||||
|
import run.halo.app.extension.Schemes;
|
||||||
|
import run.halo.app.identity.authorization.Role;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @author guqing
|
||||||
|
* @since 2.0.0
|
||||||
|
*/
|
||||||
|
@Component
|
||||||
|
public class SchemeInitializer implements ApplicationListener<ApplicationStartedEvent> {
|
||||||
|
@Override
|
||||||
|
public void onApplicationEvent(ApplicationStartedEvent event) {
|
||||||
|
Schemes.INSTANCE.register(Role.class);
|
||||||
|
}
|
||||||
|
}
|
|
@ -1,94 +0,0 @@
|
||||||
package run.halo.app.infra.types;
|
|
||||||
|
|
||||||
import java.time.LocalDateTime;
|
|
||||||
import java.util.UUID;
|
|
||||||
import lombok.Data;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* ObjectMeta is metadata that all persisted resources must have, which includes all objects
|
|
||||||
* users must create.
|
|
||||||
*
|
|
||||||
* @author guqing
|
|
||||||
* @since 2.0.0
|
|
||||||
*/
|
|
||||||
@Data
|
|
||||||
public class ObjectMeta {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Name must be unique within a namespace. Is required when creating resources, although
|
|
||||||
* some resources may allow a client to request the generation of an appropriate name
|
|
||||||
* automatically. Name is primarily intended for creation idempotence and configuration
|
|
||||||
* definition.
|
|
||||||
* Cannot be updated.
|
|
||||||
* Cannot be updated.
|
|
||||||
* More info: <a href="http://kubernetes.io/docs/user-guide/identifiers#names">names</a>
|
|
||||||
* +optional
|
|
||||||
*/
|
|
||||||
String name;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* GenerateName is an optional prefix, used by the server, to generate a unique
|
|
||||||
* name ONLY IF the Name field has not been provided.
|
|
||||||
* If this field is used, the name returned to the client will be different
|
|
||||||
* than the name passed. This value will also be combined with a unique suffix.
|
|
||||||
* The provided value has the same validation rules as the Name field,
|
|
||||||
* and may be truncated by the length of the suffix required to make the value
|
|
||||||
* unique on the server.
|
|
||||||
* <p>
|
|
||||||
* If this field is specified and the generated name exists, the server will return a 409.
|
|
||||||
* <p>
|
|
||||||
* Applied only if Name is not specified.
|
|
||||||
* More info:
|
|
||||||
* <a href="https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#idempotency">idempotency</a>
|
|
||||||
* +optional
|
|
||||||
*/
|
|
||||||
String generateName;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* UID is the unique in time and space value for this object. It is typically generated by
|
|
||||||
* the server on successful creation of a resource and is not allowed to change on PUT
|
|
||||||
* operations.
|
|
||||||
* <p>
|
|
||||||
* Populated by the system.
|
|
||||||
* Read-only.
|
|
||||||
* More info: <a href="http://kubernetes.io/docs/user-guide/identifiers#uids">uids</a>
|
|
||||||
* +optional
|
|
||||||
*/
|
|
||||||
UUID uid;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* An opaque value that represents the internal version of this object that can
|
|
||||||
* be used by clients to determine when objects have changed. May be used for optimistic
|
|
||||||
* concurrency, change detection, and the watch operation on a resource or set of resources.
|
|
||||||
* Clients must treat these values as opaque and passed unmodified back to the server.
|
|
||||||
* They may only be valid for a particular resource or set of resources.
|
|
||||||
* <p>
|
|
||||||
* Populated by the system.
|
|
||||||
* Read-only.
|
|
||||||
* Value must be treated as opaque by clients and .
|
|
||||||
* More info:
|
|
||||||
* <a href="https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#concurrency-control-and-consistency">concurrency-control-and-consistency</a>
|
|
||||||
*/
|
|
||||||
String resourceVersion;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* A sequence number representing a specific generation of the desired state.
|
|
||||||
* Populated by the system. Read-only.
|
|
||||||
* +optional
|
|
||||||
*/
|
|
||||||
Long generation;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* CreationTimestamp is a timestamp representing the server time when this object was
|
|
||||||
* created. It is not guaranteed to be set in happens-before order across separate operations.
|
|
||||||
* Clients may not set this value. It is represented in RFC3339 form and is in UTC.
|
|
||||||
* <p>
|
|
||||||
* Populated by the system.
|
|
||||||
* Read-only.
|
|
||||||
* Null for lists.
|
|
||||||
* More info:
|
|
||||||
* <a href="https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#metadata">metadata</a>
|
|
||||||
* +optional
|
|
||||||
*/
|
|
||||||
LocalDateTime creationTimestamp;
|
|
||||||
}
|
|
|
@ -1,36 +0,0 @@
|
||||||
package run.halo.app.infra.types;
|
|
||||||
|
|
||||||
import lombok.Data;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* TypeMeta describes an individual object in an API response or request
|
|
||||||
* with strings representing the type of the object and its API schema version.
|
|
||||||
* Structures that are versioned or persisted should inline TypeMeta.
|
|
||||||
*
|
|
||||||
* @author guqing
|
|
||||||
* @since 2.0.0
|
|
||||||
*/
|
|
||||||
@Data
|
|
||||||
public class TypeMeta {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Kind is a string value representing the REST resource this object represents.
|
|
||||||
* Servers may infer this from the endpoint the client submits requests to.
|
|
||||||
* Cannot be updated.
|
|
||||||
* In CamelCase.
|
|
||||||
* More info:
|
|
||||||
* <a href="https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#types-kinds>types-kinds</a>
|
|
||||||
* +optional
|
|
||||||
*/
|
|
||||||
String kind;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* APIVersion defines the versioned schema of this representation of an object.
|
|
||||||
* Servers should convert recognized schemas to the latest internal value, and
|
|
||||||
* may reject unrecognized values.
|
|
||||||
* More info:
|
|
||||||
* <a href="https://git.k8s.io/community/contributors/devel/sig-architecture/api-conventions.md#resources">resources</a>
|
|
||||||
* +optional
|
|
||||||
*/
|
|
||||||
String apiVersion;
|
|
||||||
}
|
|
|
@ -0,0 +1,59 @@
|
||||||
|
package run.halo.app.authorization;
|
||||||
|
|
||||||
|
import static org.assertj.core.api.Assertions.assertThat;
|
||||||
|
|
||||||
|
import java.util.List;
|
||||||
|
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}.
|
||||||
|
*
|
||||||
|
* @author guqing
|
||||||
|
* @since 2.0.0
|
||||||
|
*/
|
||||||
|
@ExtendWith(SpringExtension.class)
|
||||||
|
public class DefaultRoleBindingListerTest {
|
||||||
|
|
||||||
|
private DefaultRoleBindingLister roleBindingLister;
|
||||||
|
|
||||||
|
@BeforeEach
|
||||||
|
void setUp() {
|
||||||
|
roleBindingLister = new DefaultRoleBindingLister();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@WithMockUser(username = "test", roles = {"readPost", "readTag"})
|
||||||
|
void listWhenAuthorizedWithTwoRoles() {
|
||||||
|
List<RoleBinding> roleBindings = roleBindingLister.listRoleBindings();
|
||||||
|
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");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
void listWhenUnauthorizedThenEmpty() {
|
||||||
|
List<RoleBinding> roleBindings = roleBindingLister.listRoleBindings();
|
||||||
|
assertThat(roleBindings).isEmpty();
|
||||||
|
}
|
||||||
|
}
|
|
@ -13,6 +13,7 @@ import org.springframework.security.core.userdetails.User;
|
||||||
import org.springframework.util.MultiValueMap;
|
import org.springframework.util.MultiValueMap;
|
||||||
import org.springframework.web.util.UriComponents;
|
import org.springframework.web.util.UriComponents;
|
||||||
import org.springframework.web.util.UriComponentsBuilder;
|
import org.springframework.web.util.UriComponentsBuilder;
|
||||||
|
import run.halo.app.extension.Metadata;
|
||||||
import run.halo.app.identity.authorization.AttributesRecord;
|
import run.halo.app.identity.authorization.AttributesRecord;
|
||||||
import run.halo.app.identity.authorization.DefaultRuleResolver;
|
import run.halo.app.identity.authorization.DefaultRuleResolver;
|
||||||
import run.halo.app.identity.authorization.PolicyRule;
|
import run.halo.app.identity.authorization.PolicyRule;
|
||||||
|
@ -23,7 +24,6 @@ import run.halo.app.identity.authorization.Role;
|
||||||
import run.halo.app.identity.authorization.RoleBinding;
|
import run.halo.app.identity.authorization.RoleBinding;
|
||||||
import run.halo.app.identity.authorization.RoleRef;
|
import run.halo.app.identity.authorization.RoleRef;
|
||||||
import run.halo.app.identity.authorization.Subject;
|
import run.halo.app.identity.authorization.Subject;
|
||||||
import run.halo.app.infra.types.ObjectMeta;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tests for {@link RequestInfoFactory}.
|
* Tests for {@link RequestInfoFactory}.
|
||||||
|
@ -131,13 +131,16 @@ public class RequestInfoResolverTest {
|
||||||
.build()
|
.build()
|
||||||
);
|
);
|
||||||
role.setRules(rules);
|
role.setRules(rules);
|
||||||
ObjectMeta objectMeta = new ObjectMeta();
|
Metadata metadata = new Metadata();
|
||||||
objectMeta.setName("ruleReadPost");
|
metadata.setName("ruleReadPost");
|
||||||
role.setObjectMeta(objectMeta);
|
role.setMetadata(metadata);
|
||||||
return role;
|
return role;
|
||||||
}, () -> {
|
}, () -> {
|
||||||
// role binding lister
|
// role binding lister
|
||||||
RoleBinding roleBinding = new RoleBinding();
|
RoleBinding roleBinding = new RoleBinding();
|
||||||
|
Metadata metadata = new Metadata();
|
||||||
|
metadata.setName("admin_ruleReadPost");
|
||||||
|
roleBinding.setMetadata(metadata);
|
||||||
|
|
||||||
Subject subject = new Subject();
|
Subject subject = new Subject();
|
||||||
subject.setName("admin");
|
subject.setName("admin");
|
||||||
|
|
|
@ -36,6 +36,7 @@ import org.springframework.security.web.access.intercept.FilterSecurityIntercept
|
||||||
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;
|
import org.springframework.security.web.authentication.www.BasicAuthenticationFilter;
|
||||||
import org.springframework.security.web.context.SecurityContextPersistenceFilter;
|
import org.springframework.security.web.context.SecurityContextPersistenceFilter;
|
||||||
import org.springframework.test.context.TestPropertySource;
|
import org.springframework.test.context.TestPropertySource;
|
||||||
|
import run.halo.app.extension.Metadata;
|
||||||
import run.halo.app.identity.authentication.InMemoryOAuth2AuthorizationService;
|
import run.halo.app.identity.authentication.InMemoryOAuth2AuthorizationService;
|
||||||
import run.halo.app.identity.authentication.JwtGenerator;
|
import run.halo.app.identity.authentication.JwtGenerator;
|
||||||
import run.halo.app.identity.authentication.OAuth2AuthorizationService;
|
import run.halo.app.identity.authentication.OAuth2AuthorizationService;
|
||||||
|
@ -53,7 +54,6 @@ import run.halo.app.identity.authorization.RoleBinding;
|
||||||
import run.halo.app.identity.authorization.RoleRef;
|
import run.halo.app.identity.authorization.RoleRef;
|
||||||
import run.halo.app.identity.authorization.Subject;
|
import run.halo.app.identity.authorization.Subject;
|
||||||
import run.halo.app.infra.properties.JwtProperties;
|
import run.halo.app.infra.properties.JwtProperties;
|
||||||
import run.halo.app.infra.types.ObjectMeta;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @author guqing
|
* @author guqing
|
||||||
|
@ -115,17 +115,17 @@ public class TestWebSecurityConfig {
|
||||||
.build()
|
.build()
|
||||||
);
|
);
|
||||||
role.setRules(rules);
|
role.setRules(rules);
|
||||||
ObjectMeta objectMeta = new ObjectMeta();
|
Metadata metadata = new Metadata();
|
||||||
objectMeta.setName("ruleReadPost");
|
metadata.setName("ruleReadPost");
|
||||||
role.setObjectMeta(objectMeta);
|
role.setMetadata(metadata);
|
||||||
return role;
|
return role;
|
||||||
}, () -> {
|
}, () -> {
|
||||||
// role binding lister
|
// role binding lister
|
||||||
RoleBinding roleBinding = new RoleBinding();
|
RoleBinding roleBinding = new RoleBinding();
|
||||||
|
|
||||||
ObjectMeta objectMeta = new ObjectMeta();
|
Metadata metadata = new Metadata();
|
||||||
objectMeta.setName("userRoleBinding");
|
metadata.setName("userRoleBinding");
|
||||||
roleBinding.setObjectMeta(objectMeta);
|
roleBinding.setMetadata(metadata);
|
||||||
|
|
||||||
Subject subject = new Subject();
|
Subject subject = new Subject();
|
||||||
subject.setName("test_user");
|
subject.setName("test_user");
|
||||||
|
|
Loading…
Reference in New Issue