Skip to content

Commit

Permalink
Merge remote-tracking branch 'upstream/main'
Browse files Browse the repository at this point in the history
  • Loading branch information
ruibaby committed Sep 25, 2023
2 parents 28ba181 + 5fa0056 commit 1196dba
Show file tree
Hide file tree
Showing 75 changed files with 4,002 additions and 781 deletions.
7 changes: 4 additions & 3 deletions .github/ISSUE_TEMPLATE/bug_report.en.yml
Original file line number Diff line number Diff line change
Expand Up @@ -30,15 +30,16 @@ body:
- MariaDB
- Other
- type: dropdown
id: deployment-method
id: operation-method
validations:
required: true
attributes:
label: "What is your deployment method?"
label: "What is the project operation method?"
options:
- Docker
- Docker Compose
- Fat Jar
- Source Code
- type: input
id: site-url
attributes:
Expand All @@ -55,7 +56,7 @@ body:
id: what-happened
attributes:
label: "What happened?"
description: "Also tell us, what did you expect to happen?"
description: "For ease of management, please do not report multiple unrelated issues under the same issue."
validations:
required: true
- type: textarea
Expand Down
7 changes: 4 additions & 3 deletions .github/ISSUE_TEMPLATE/bug_report.zh.yml
Original file line number Diff line number Diff line change
Expand Up @@ -31,15 +31,16 @@ body:
- MariaDB
- Other
- type: dropdown
id: deployment-method
id: operation-method
validations:
required: true
attributes:
label: "使用的哪种方式部署"
label: "使用的哪种方式运行"
options:
- Docker
- Docker Compose
- Fat Jar
- Source Code
- type: input
id: site-url
attributes:
Expand All @@ -56,7 +57,7 @@ body:
id: what-happened
attributes:
label: "发生了什么?"
description: "最好还告诉我们,你预计会发生什么"
description: "为了方便我们管理,请不要在同一个 issue 下报告多个不相关的问题"
validations:
required: true
- type: textarea
Expand Down
1 change: 1 addition & 0 deletions .github/ISSUE_TEMPLATE/feature_request.en.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ body:
id: description
attributes:
label: "Describe this feature"
description: "For ease of management, please do not submit multiple unrelated features under the same issue."
validations:
required: true
- type: textarea
Expand Down
1 change: 1 addition & 0 deletions .github/ISSUE_TEMPLATE/feature_request.zh.yml
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,7 @@ body:
id: description
attributes:
label: "描述一下此特性"
description: "为了方便我们管理,请不要在同一个 issue 下提交多个没有相关性的特性。"
validations:
required: true
- type: textarea
Expand Down
2 changes: 1 addition & 1 deletion .github/workflows/halo.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -60,5 +60,5 @@ jobs:
ghcr-token: ${{ secrets.GHCR_TOKEN }}
dockerhub-user: ${{ secrets.DOCKER_USERNAME }}
dockerhub-token: ${{ secrets.DOCKER_TOKEN }}
push: ${{ github.event_name == 'push' || github.event_name == 'release' }} # we only push to GHCR if the push is to the next branch
push: ${{ github.event_name == 'pull_request' || github.event_name == 'push' || github.event_name == 'release' }}
console-ref: ${{ github.event_name == 'release' && github.ref || 'main' }}
2 changes: 2 additions & 0 deletions api/src/main/java/run/halo/app/core/extension/Role.java
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,8 @@ public class Role extends AbstractExtension {

public static final String SYSTEM_RESERVED_LABELS =
"rbac.authorization.halo.run/system-reserved";
public static final String HIDDEN_LABEL_NAME = "halo.run/hidden";
public static final String TEMPLATE_LABEL_NAME = "halo.run/role-template";
public static final String UI_PERMISSIONS_AGGREGATED_ANNO =
"rbac.authorization.halo.run/ui-permissions-aggregated";

Expand Down
53 changes: 53 additions & 0 deletions api/src/main/java/run/halo/app/security/PersonalAccessToken.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
package run.halo.app.security;

import static io.swagger.v3.oas.annotations.media.Schema.RequiredMode.REQUIRED;

import io.swagger.v3.oas.annotations.media.Schema;
import java.time.Instant;
import java.util.List;
import lombok.Data;
import lombok.EqualsAndHashCode;
import lombok.ToString;
import run.halo.app.extension.AbstractExtension;
import run.halo.app.extension.GVK;

@Data
@ToString(callSuper = true)
@EqualsAndHashCode(callSuper = true)
@GVK(group = "security.halo.run", version = "v1alpha1", kind = PersonalAccessToken.KIND,
plural = "personalaccesstokens", singular = "personalaccesstoken")
public class PersonalAccessToken extends AbstractExtension {

public static final String KIND = "PersonalAccessToken";

private Spec spec = new Spec();

@Data
@Schema(name = "PatSpec")
public static class Spec {

@Schema(requiredMode = REQUIRED)
private String name;

private String description;

private Instant expiresAt;

private List<String> roles;

private List<String> scopes;

@Schema(requiredMode = REQUIRED)
private String username;

private boolean revoked;

private Instant revokesAt;

private Instant lastUsed;

@Schema(requiredMode = REQUIRED)
private String tokenId;

}
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
package run.halo.app.config;

import static org.springframework.security.config.Customizer.withDefaults;
import static org.springframework.security.web.server.authentication.ServerWebExchangeDelegatingReactiveAuthenticationManagerResolver.builder;
import static org.springframework.security.web.server.util.matcher.ServerWebExchangeMatchers.pathMatchers;

import java.util.Set;
Expand All @@ -26,6 +27,7 @@
import org.springframework.web.reactive.function.server.ServerResponse;
import run.halo.app.core.extension.service.RoleService;
import run.halo.app.core.extension.service.UserService;
import run.halo.app.extension.ReactiveExtensionClient;
import run.halo.app.infra.AnonymousUserConst;
import run.halo.app.infra.properties.HaloProperties;
import run.halo.app.plugin.extensionpoint.ExtensionGetter;
Expand All @@ -37,6 +39,9 @@
import run.halo.app.security.authentication.login.PublicKeyRouteBuilder;
import run.halo.app.security.authentication.login.RsaKeyScheduledGenerator;
import run.halo.app.security.authentication.login.impl.RsaKeyService;
import run.halo.app.security.authentication.pat.PatAuthenticationManager;
import run.halo.app.security.authentication.pat.PatJwkSupplier;
import run.halo.app.security.authentication.pat.PatServerWebExchangeMatcher;
import run.halo.app.security.authorization.RequestInfoAuthorizationManager;

/**
Expand All @@ -55,7 +60,9 @@ SecurityWebFilterChain apiFilterChain(ServerHttpSecurity http,
RoleService roleService,
ObjectProvider<SecurityConfigurer> securityConfigurers,
ServerSecurityContextRepository securityContextRepository,
ExtensionGetter extensionGetter) {
ExtensionGetter extensionGetter,
ReactiveExtensionClient client,
PatJwkSupplier patJwkSupplier) {

http.securityMatcher(pathMatchers("/api/**", "/apis/**", "/oauth2/**",
"/login/**", "/logout", "/actuator/**"))
Expand All @@ -68,6 +75,14 @@ SecurityWebFilterChain apiFilterChain(ServerHttpSecurity http,
})
.securityContextRepository(securityContextRepository)
.httpBasic(withDefaults())
.oauth2ResourceServer(oauth2 -> {
var authManagerResolver = builder().add(
new PatServerWebExchangeMatcher(),
new PatAuthenticationManager(client, patJwkSupplier))
// TODO Add other authentication mangers here. e.g.: JwtAuthentiationManager.
.build();
oauth2.authenticationManagerResolver(authManagerResolver);
})
.exceptionHandling(
spec -> spec.authenticationEntryPoint(new DefaultServerAuthenticationEntryPoint()));

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
import static run.halo.app.extension.ListResult.generateGenericClass;
import static run.halo.app.extension.router.QueryParamBuildUtil.buildParametersFromType;
import static run.halo.app.extension.router.selector.SelectorUtil.labelAndFieldSelectorToPredicate;
import static run.halo.app.security.authorization.AuthorityUtils.authoritiesToRoles;

import com.fasterxml.jackson.core.type.TypeReference;
import com.google.common.io.Files;
Expand All @@ -21,6 +22,7 @@
import io.swagger.v3.oas.annotations.media.Schema;
import java.time.Duration;
import java.util.ArrayList;
import java.util.Collections;
import java.util.Comparator;
import java.util.HashMap;
import java.util.HashSet;
Expand All @@ -31,8 +33,9 @@
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.atomic.AtomicInteger;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import lombok.Data;
import lombok.RequiredArgsConstructor;
import org.apache.commons.lang3.StringUtils;
import org.springdoc.core.fn.builders.requestbody.Builder;
Expand Down Expand Up @@ -483,45 +486,86 @@ record GrantRequest(Set<String> roles) {

@NonNull
private Mono<ServerResponse> getUserPermission(ServerRequest request) {
String name = request.pathVariable("name");
return ReactiveSecurityContextHolder.getContext()
.map(ctx -> SELF_USER.equals(name) ? ctx.getAuthentication().getName() : name)
.flatMapMany(userService::listRoles)
.reduce(new LinkedHashSet<Role>(), (list, role) -> {
list.add(role);
return list;
})
.flatMap(roles -> uiPermissions(roles)
.collectList()
.map(uiPermissions -> new UserPermission(roles, Set.copyOf(uiPermissions)))
.defaultIfEmpty(new UserPermission(roles, Set.of()))
)
.flatMap(result -> ServerResponse.ok()
.contentType(MediaType.APPLICATION_JSON)
.bodyValue(result)
);
var name = request.pathVariable("name");
Mono<UserPermission> userPermission;
if (SELF_USER.equals(name)) {
userPermission = ReactiveSecurityContextHolder.getContext()
.map(SecurityContext::getAuthentication)
.flatMap(auth -> {
var roleNames = authoritiesToRoles(auth.getAuthorities());
var up = new UserPermission();
var roles = roleService.list(roleNames)
.collect(Collectors.toSet())
.doOnNext(up::setRoles)
.then();
var permissions = roleService.listPermissions(roleNames)
.distinct()
.collectList()
.doOnNext(up::setPermissions)
.doOnNext(perms -> {
var uiPermissions = uiPermissions(new HashSet<>(perms));
up.setUiPermissions(uiPermissions);
})
.then();
return roles.and(permissions).thenReturn(up);
});
} else {
// get roles from username
userPermission = userService.listRoles(name)
.collect(Collectors.toSet())
.flatMap(roles -> {
var up = new UserPermission();
var setRoles = Mono.fromRunnable(() -> up.setRoles(roles)).then();
var roleNames = roles.stream()
.map(role -> role.getMetadata().getName())
.collect(Collectors.toSet());
var setPermissions = roleService.listPermissions(roleNames)
.distinct()
.collectList()
.doOnNext(up::setPermissions)
.doOnNext(perms -> {
var uiPermissions = uiPermissions(new HashSet<>(perms));
up.setUiPermissions(uiPermissions);
})
.then();
return setRoles.and(setPermissions).thenReturn(up);
});
}

return ServerResponse.ok().body(userPermission, UserPermission.class);
}

private Flux<String> uiPermissions(Set<Role> roles) {
return Flux.fromIterable(roles)
.map(role -> role.getMetadata().getName())
.collectList()
.flatMapMany(roleNames -> roleService.listDependenciesFlux(Set.copyOf(roleNames)))
.map(role -> {
Map<String, String> annotations = MetadataUtil.nullSafeAnnotations(role);
String uiPermissionStr = annotations.get(Role.UI_PERMISSIONS_ANNO);
if (StringUtils.isBlank(uiPermissionStr)) {
return new HashSet<String>();
private Set<String> uiPermissions(Set<Role> roles) {
if (CollectionUtils.isEmpty(roles)) {
return Collections.emptySet();
}
return roles.stream()
.<Set<String>>map(role -> {
var annotations = role.getMetadata().getAnnotations();
if (annotations == null) {
return Set.of();
}
return JsonUtils.jsonToObject(uiPermissionStr,
var uiPermissionsJson = annotations.get(Role.UI_PERMISSIONS_ANNO);
if (StringUtils.isBlank(uiPermissionsJson)) {
return Set.of();
}
return JsonUtils.jsonToObject(uiPermissionsJson,
new TypeReference<LinkedHashSet<String>>() {
});
})
.flatMapIterable(Function.identity());
.flatMap(Set::stream)
.collect(Collectors.toSet());
}

record UserPermission(@Schema(requiredMode = REQUIRED) Set<Role> roles,
@Schema(requiredMode = REQUIRED) Set<String> uiPermissions) {
@Data
public static class UserPermission {
@Schema(requiredMode = REQUIRED)
private Set<Role> roles;
@Schema(requiredMode = REQUIRED)
private List<Role> permissions;
@Schema(requiredMode = REQUIRED)
private Set<String> uiPermissions;

}

public class ListRequest extends IListRequest.QueryListRequest {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -142,7 +142,7 @@ boolean readinessDetection(String name) {
if (waitForSettingCreation(plugin)) {
return true;
}
createInitialReverseProxyIfNotPresent(plugin);
recreateDefaultReverseProxy(plugin);

updateStatus(name, status -> {
String logoUrl = generateAccessibleLogoUrl(plugin);
Expand Down Expand Up @@ -764,7 +764,7 @@ Path determinePluginLocation(Plugin plugin) {
return Paths.get(pluginLocation);
}

void createInitialReverseProxyIfNotPresent(Plugin plugin) {
void recreateDefaultReverseProxy(Plugin plugin) {
String pluginName = plugin.getMetadata().getName();
String reverseProxyName = initialReverseProxyName(pluginName);
ReverseProxy reverseProxy = new ReverseProxy();
Expand All @@ -785,11 +785,9 @@ void createInitialReverseProxyIfNotPresent(Plugin plugin) {

client.fetch(ReverseProxy.class, reverseProxyName)
.ifPresentOrElse(persisted -> {
if (isDevelopmentMode(pluginName)) {
reverseProxy.getMetadata()
.setVersion(persisted.getMetadata().getVersion());
client.update(reverseProxy);
}
reverseProxy.getMetadata()
.setVersion(persisted.getMetadata().getVersion());
client.update(reverseProxy);
}, () -> client.create(reverseProxy));
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import lombok.extern.slf4j.Slf4j;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.web.authentication.AnonymousAuthenticationFilter;
import run.halo.app.security.authorization.AuthorityUtils;

/**
* <p>Obtain the authorities from the authenticated authentication and construct it as a RoleBinding
Expand All @@ -21,8 +22,8 @@
*/
@Slf4j
public class DefaultRoleBindingService implements RoleBindingService {
private static final String SCOPE_AUTHORITY_PREFIX = "SCOPE_";
private static final String ROLE_AUTHORITY_PREFIX = "ROLE_";
private static final String SCOPE_AUTHORITY_PREFIX = AuthorityUtils.SCOPE_PREFIX;
private static final String ROLE_AUTHORITY_PREFIX = AuthorityUtils.ROLE_PREFIX;

@Override
public Set<String> listBoundRoleNames(Collection<? extends GrantedAuthority> authorities) {
Expand Down
Loading

0 comments on commit 1196dba

Please sign in to comment.