|
@@ -1,38 +1,41 @@
|
|
|
package org.jetlinks.community.auth.web;
|
|
|
|
|
|
import io.swagger.v3.oas.annotations.Operation;
|
|
|
+import io.swagger.v3.oas.annotations.Parameter;
|
|
|
import io.swagger.v3.oas.annotations.tags.Tag;
|
|
|
import lombok.AllArgsConstructor;
|
|
|
-import org.hswebframework.web.api.crud.entity.TreeSupportEntity;
|
|
|
+import lombok.Getter;
|
|
|
+import lombok.NoArgsConstructor;
|
|
|
+import lombok.Setter;
|
|
|
+import org.hswebframework.ezorm.core.param.TermType;
|
|
|
+import org.hswebframework.ezorm.rdb.mapping.defaults.SaveResult;
|
|
|
+import org.hswebframework.web.api.crud.entity.*;
|
|
|
import org.hswebframework.web.authorization.Authentication;
|
|
|
-import org.hswebframework.web.authorization.annotation.Authorize;
|
|
|
-import org.hswebframework.web.authorization.annotation.Resource;
|
|
|
-import org.hswebframework.web.authorization.annotation.ResourceAction;
|
|
|
+import org.hswebframework.web.authorization.annotation.*;
|
|
|
import org.hswebframework.web.authorization.exception.UnAuthorizedException;
|
|
|
import org.hswebframework.web.crud.service.ReactiveCrudService;
|
|
|
import org.hswebframework.web.crud.web.reactive.ReactiveServiceCrudController;
|
|
|
-import org.jetlinks.community.auth.entity.MenuButtonInfo;
|
|
|
+import org.hswebframework.web.i18n.LocaleUtils;
|
|
|
+import org.hswebframework.web.system.authorization.defaults.service.DefaultPermissionService;
|
|
|
+import org.jetlinks.community.auth.configuration.MenuProperties;
|
|
|
import org.jetlinks.community.auth.entity.MenuEntity;
|
|
|
import org.jetlinks.community.auth.entity.MenuView;
|
|
|
-import org.jetlinks.community.auth.service.AuthorizationSettingDetailService;
|
|
|
import org.jetlinks.community.auth.service.DefaultMenuService;
|
|
|
-import org.jetlinks.community.auth.web.request.MenuGrantRequest;
|
|
|
-import org.springframework.util.StringUtils;
|
|
|
+import org.jetlinks.community.auth.service.MenuGrantService;
|
|
|
+import org.jetlinks.community.auth.service.request.request.MenuGrantRequest;
|
|
|
+import org.jetlinks.community.auth.web.request.AuthorizationSettingDetail;
|
|
|
+import org.springframework.transaction.annotation.Transactional;
|
|
|
import org.springframework.web.bind.annotation.*;
|
|
|
import reactor.core.publisher.Flux;
|
|
|
import reactor.core.publisher.Mono;
|
|
|
|
|
|
-import java.util.Collection;
|
|
|
-import java.util.HashMap;
|
|
|
-import java.util.Map;
|
|
|
+import java.util.List;
|
|
|
import java.util.function.Function;
|
|
|
-import java.util.function.Predicate;
|
|
|
-import java.util.stream.Collectors;
|
|
|
|
|
|
/**
|
|
|
* 菜单管理
|
|
|
*
|
|
|
- * @author wangzheng
|
|
|
+ * @author zhouhao
|
|
|
* @since 1.0
|
|
|
*/
|
|
|
@RestController
|
|
@@ -45,150 +48,260 @@ public class MenuController implements ReactiveServiceCrudController<MenuEntity,
|
|
|
|
|
|
private final DefaultMenuService defaultMenuService;
|
|
|
|
|
|
- private final AuthorizationSettingDetailService settingService;
|
|
|
+ private final MenuGrantService grantService;
|
|
|
+
|
|
|
+ private final MenuProperties properties;
|
|
|
+
|
|
|
+ private final DefaultPermissionService permissionService;
|
|
|
|
|
|
@Override
|
|
|
public ReactiveCrudService<MenuEntity, String> getService() {
|
|
|
return defaultMenuService;
|
|
|
}
|
|
|
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 获取获取全部菜单信息(树结构)
|
|
|
+ *
|
|
|
+ * @return 菜单列表
|
|
|
+ */
|
|
|
+ @QueryAction
|
|
|
+ @PostMapping("/_all/tree")
|
|
|
+ @Operation(summary = "获取获取全部菜单信息(树结构)")
|
|
|
+ public Flux<MenuEntity> getAllMenuAsTree(@RequestBody Mono<QueryParamEntity> queryMono) {
|
|
|
+ return queryMono
|
|
|
+ .doOnNext(query -> query
|
|
|
+ .toQuery()
|
|
|
+ .orderByAsc(MenuEntity::getSortIndex)
|
|
|
+ .noPaging())
|
|
|
+ .flatMapMany(defaultMenuService::query)
|
|
|
+ .collectList()
|
|
|
+ .flatMapIterable(list -> TreeSupportEntity.list2tree(list, MenuEntity::setChildren));
|
|
|
+ }
|
|
|
+
|
|
|
/**
|
|
|
* 获取用户自己的菜单列表
|
|
|
*
|
|
|
* @return 菜单列表
|
|
|
*/
|
|
|
- @GetMapping("/user-own/tree")
|
|
|
+ @PostMapping("/user-own/tree")
|
|
|
@Authorize(merge = false)
|
|
|
@Operation(summary = "获取当前用户可访问的菜单(树结构)")
|
|
|
- public Flux<MenuView> getUserMenuAsTree() {
|
|
|
+ public Flux<MenuView> getUserMenuAsTree(@RequestBody(required = false) Mono<QueryParamEntity> queryMono) {
|
|
|
+ return queryMono
|
|
|
+ .flatMapMany(queryParam -> getUserMenuAsList(queryParam)
|
|
|
+ .as(MenuController::listToTree));
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 获取用户自己的菜单列表
|
|
|
+ *
|
|
|
+ * @return 菜单列表
|
|
|
+ */
|
|
|
+ @GetMapping("/user-own/tree")
|
|
|
+ @Authorize(merge = false)
|
|
|
+ @QueryNoPagingOperation(summary = "获取当前用户可访问的菜单(树结构)")
|
|
|
+ public Flux<MenuView> getUserMenuAsTree(QueryParamEntity queryParam) {
|
|
|
return this
|
|
|
- .getUserMenuAsList()
|
|
|
+ .getUserMenuAsList(queryParam)
|
|
|
.as(MenuController::listToTree);
|
|
|
}
|
|
|
|
|
|
-
|
|
|
@GetMapping("/user-own/list")
|
|
|
@Authorize(merge = false)
|
|
|
- @Operation(summary = "获取当前用户可访问的菜单(列表结构)")
|
|
|
- public Flux<MenuView> getUserMenuAsList() {
|
|
|
+ @QueryNoPagingOperation(summary = "获取当前用户可访问的菜单(列表结构)")
|
|
|
+ public Flux<MenuView> getUserMenuAsList(QueryParamEntity queryParam) {
|
|
|
return Authentication
|
|
|
.currentReactive()
|
|
|
.switchIfEmpty(Mono.error(UnAuthorizedException::new))
|
|
|
- .flatMapMany(autz -> defaultMenuService
|
|
|
- .createQuery()
|
|
|
- .where(MenuEntity::getStatus,1)
|
|
|
- .fetch()
|
|
|
- .collect(Collectors.toMap(MenuEntity::getId, Function.identity()))
|
|
|
- .flatMapIterable(menuMap -> MenuController
|
|
|
- .convertMenuView(menuMap,
|
|
|
- menu -> "admin".equals(autz.getUser().getUsername()) ||
|
|
|
- menu.hasPermission(autz::hasPermission),
|
|
|
- button -> "admin".equals(autz.getUser().getUsername()) ||
|
|
|
- button.hasPermission(autz::hasPermission)
|
|
|
- )));
|
|
|
- }
|
|
|
-
|
|
|
- @PutMapping("/_grant")
|
|
|
- @Operation(summary = "根据菜单进行授权")
|
|
|
+ .flatMapMany(autz -> properties.isAllowAllMenu(autz)
|
|
|
+ ?
|
|
|
+ defaultMenuService
|
|
|
+ .getMenuViews(queryParam, menu -> true)
|
|
|
+ .doOnNext(MenuView::grantAll)
|
|
|
+ :
|
|
|
+ defaultMenuService.getGrantedMenus(queryParam, autz.getDimensions())
|
|
|
+ );
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 获取菜单所属系统
|
|
|
+ * 用于新增应用管理时,根据所属系统(owner)查询菜单信息
|
|
|
+ *
|
|
|
+ * @return 所属系统owner
|
|
|
+ */
|
|
|
+ @Authorize(ignore = true)
|
|
|
+ @PostMapping("/owner")
|
|
|
+ @Operation(summary = "获取菜单所属系统")
|
|
|
+ public Flux<String> getSystemMenuOwner(@RequestBody @Parameter(description = "需要去除的所属系统,例如当前系统")
|
|
|
+ Mono<List<String>> excludes) {
|
|
|
+ return excludes.flatMapMany(owner -> defaultMenuService
|
|
|
+ .createQuery()
|
|
|
+ .and(MenuEntity::getOwner, TermType.nin, owner)
|
|
|
+ .fetch())
|
|
|
+ .map(MenuEntity::getOwner)
|
|
|
+ .distinct();
|
|
|
+ }
|
|
|
+
|
|
|
+ /**
|
|
|
+ * 获取本系统菜单信息(树结构)
|
|
|
+ * 用于应用管理之间同步菜单
|
|
|
+ *
|
|
|
+ * @return 菜单列表
|
|
|
+ */
|
|
|
+ @Authorize(ignore = true)
|
|
|
+ @PostMapping("/owner/tree/{owner}")
|
|
|
+ @Operation(summary = "获取本系统菜单信息(树结构)")
|
|
|
+ public Flux<MenuEntity> getSystemMenuAsTree(@PathVariable @Parameter(description = "菜单所属系统") String owner,
|
|
|
+ @RequestBody Mono<QueryParamEntity> queryMono) {
|
|
|
+ return queryMono
|
|
|
+ .doOnNext(query -> query
|
|
|
+ .toQuery()
|
|
|
+ .and(MenuEntity::getOwner, owner)
|
|
|
+ .orderByAsc(MenuEntity::getSortIndex)
|
|
|
+ .noPaging())
|
|
|
+ .flatMapMany(defaultMenuService::query)
|
|
|
+ .collectList()
|
|
|
+ .flatMapIterable(list -> TreeSupportEntity.list2tree(list, MenuEntity::setChildren));
|
|
|
+ }
|
|
|
+
|
|
|
+ @PutMapping("/{targetType}/{targetId}/_grant")
|
|
|
+ @Operation(summary = "对菜单进行授权")
|
|
|
@ResourceAction(id = "grant", name = "授权")
|
|
|
- public Mono<Void> grant(@RequestBody Mono<MenuGrantRequest> body) {
|
|
|
- return Mono
|
|
|
- .zip(
|
|
|
- //T1: 当前用户权限信息
|
|
|
- Authentication.currentReactive(),
|
|
|
- //T2: 将菜单信息转为授权信息
|
|
|
- Mono
|
|
|
- .zip(body,
|
|
|
- defaultMenuService
|
|
|
- .createQuery()
|
|
|
- .where(MenuEntity::getStatus,1)
|
|
|
- .fetch()
|
|
|
- .collectList(),
|
|
|
- MenuGrantRequest::toAuthorizationSettingDetail
|
|
|
- )
|
|
|
- .map(Flux::just),
|
|
|
- //保存授权信息
|
|
|
- settingService::saveDetail
|
|
|
- )
|
|
|
- .flatMap(Function.identity());
|
|
|
+ public Mono<Void> grant(@PathVariable String targetType,
|
|
|
+ @PathVariable String targetId,
|
|
|
+ @RequestBody Mono<MenuGrantRequest> body) {
|
|
|
+ //todo 防越权控制
|
|
|
+ return body
|
|
|
+ .doOnNext(request -> {
|
|
|
+ request.setTargetType(targetType);
|
|
|
+ request.setTargetId(targetId);
|
|
|
+ })
|
|
|
+ .flatMap(grantService::grant);
|
|
|
+ }
|
|
|
+
|
|
|
+ @PutMapping("/_batch/_grant")
|
|
|
+ @Operation(summary = "对菜单批量进行授权")
|
|
|
+ @ResourceAction(id = "grant", name = "授权")
|
|
|
+ public Mono<Void> grant(@RequestBody Flux<MenuGrantRequest> body) {
|
|
|
+ return body
|
|
|
+ .flatMap(grantService::grant)
|
|
|
+ .then();
|
|
|
}
|
|
|
|
|
|
@GetMapping("/{targetType}/{targetId}/_grant/tree")
|
|
|
@ResourceAction(id = "grant", name = "授权")
|
|
|
- @Operation(summary = "获取菜单授权信息(树结构)")
|
|
|
+ @QueryNoPagingOperation(summary = "获取菜单授权信息(树结构)")
|
|
|
public Flux<MenuView> getGrantInfoTree(@PathVariable String targetType,
|
|
|
- @PathVariable String targetId) {
|
|
|
+ @PathVariable String targetId,
|
|
|
+ QueryParamEntity query) {
|
|
|
|
|
|
return this
|
|
|
- .getGrantInfo(targetType, targetId)
|
|
|
+ .getGrantInfo(targetType, targetId, query)
|
|
|
.as(MenuController::listToTree);
|
|
|
}
|
|
|
|
|
|
@GetMapping("/{targetType}/{targetId}/_grant/list")
|
|
|
@ResourceAction(id = "grant", name = "授权")
|
|
|
- @Operation(summary = "获取菜单授权信息(列表结构)")
|
|
|
+ @QueryNoPagingOperation(summary = "获取菜单授权信息(列表结构)")
|
|
|
public Flux<MenuView> getGrantInfo(@PathVariable String targetType,
|
|
|
- @PathVariable String targetId) {
|
|
|
+ @PathVariable String targetId,
|
|
|
+ QueryParamEntity query) {
|
|
|
|
|
|
+ Flux<MenuView> allMenu = this.getUserMenuAsList(query).cache();
|
|
|
return Mono
|
|
|
.zip(
|
|
|
- //权限设置信息
|
|
|
- settingService.getSettingDetail(targetType, targetId),
|
|
|
- //菜单
|
|
|
defaultMenuService
|
|
|
- .createQuery()
|
|
|
- .where(MenuEntity::getStatus,1)
|
|
|
- .fetch()
|
|
|
- .collectMap(MenuEntity::getId, Function.identity()),
|
|
|
- (detail, menuMap) -> MenuController
|
|
|
- .convertMenuView(menuMap,
|
|
|
- menu -> menu.hasPermission(detail::hasPermission),
|
|
|
- button -> button.hasPermission(detail::hasPermission)
|
|
|
- )
|
|
|
- )
|
|
|
- .flatMapIterable(Function.identity());
|
|
|
+ .getGrantedMenus(targetType, targetId)
|
|
|
+ .collectMap(GenericEntity::getId),
|
|
|
+ allMenu.collectMap(MenuView::getId, Function.identity()),
|
|
|
+ (granted, all) -> LocaleUtils
|
|
|
+ .currentReactive()
|
|
|
+ .flatMapMany(locale -> allMenu
|
|
|
+ .doOnNext(MenuView::resetGrant)
|
|
|
+ .map(view -> view
|
|
|
+ .withGranted(granted.get(view.getId()))
|
|
|
+ )))
|
|
|
+ .flatMapMany(Function.identity());
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ @PostMapping("/permissions")
|
|
|
+ @ResourceAction(id = "grant", name = "授权")
|
|
|
+ @Operation(summary = "根据菜单获取对应的权限")
|
|
|
+ public Flux<AuthorizationSettingDetail.PermissionInfo> getPermissionsByMenuGrant(@RequestBody Flux<MenuView> menus) {
|
|
|
+ return getAuthorizationSettingDetail(menus)
|
|
|
+ .flatMapIterable(AuthorizationSettingDetail::getPermissionList);
|
|
|
+ }
|
|
|
+
|
|
|
+
|
|
|
+ @PostMapping("/asset-types")
|
|
|
+ @ResourceAction(id = "grant", name = "授权")
|
|
|
+ @Operation(summary = "根据菜单获取对应的资产类型")
|
|
|
+ @Deprecated
|
|
|
+ public Flux<AssetTypeView> getAssetTypeByMenuGrant(@RequestBody Flux<MenuView> menus) {
|
|
|
+ // 社区版本目前不支持数据权限控制
|
|
|
+ return Flux.empty();
|
|
|
+
|
|
|
+ }
|
|
|
+
|
|
|
+ @PatchMapping("/_all")
|
|
|
+ @SaveAction
|
|
|
+ @Transactional
|
|
|
+ @Operation(summary = "全量保存数据", description = "先删除旧数据,再新增数据")
|
|
|
+ public Mono<SaveResult> saveAll(@RequestBody Flux<MenuEntity> menus) {
|
|
|
+ return this
|
|
|
+ .getService()
|
|
|
+ .createDelete()
|
|
|
+ .where(MenuEntity::getStatus, 1)
|
|
|
+ .execute()
|
|
|
+ .then(
|
|
|
+ this.save(menus)
|
|
|
+ );
|
|
|
+ }
|
|
|
+
|
|
|
+ private Mono<AuthorizationSettingDetail> getAuthorizationSettingDetail(Flux<MenuView> menus) {
|
|
|
+ return Mono
|
|
|
+ .zip(menus
|
|
|
+ .doOnNext(view -> {
|
|
|
+ view.setGranted(true);
|
|
|
+ if (null != view.getButtons()) {
|
|
|
+ for (MenuView.ButtonView button : view.getButtons()) {
|
|
|
+ button.setGranted(true);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ })
|
|
|
+ .collectList()
|
|
|
+ .map(list -> {
|
|
|
+ MenuGrantRequest request = new MenuGrantRequest();
|
|
|
+ request.setTargetType("temp");
|
|
|
+ request.setTargetId("temp");
|
|
|
+ request.setMenus(list);
|
|
|
+ return request;
|
|
|
+ }),
|
|
|
+ defaultMenuService
|
|
|
+ .createQuery()
|
|
|
+ .fetch()
|
|
|
+ .collectList(),
|
|
|
+ MenuGrantRequest::toAuthorizationSettingDetail
|
|
|
+ );
|
|
|
}
|
|
|
|
|
|
private static Flux<MenuView> listToTree(Flux<MenuView> flux) {
|
|
|
return flux
|
|
|
.collectList()
|
|
|
- .flatMapIterable(list -> TreeSupportEntity
|
|
|
- .list2tree(list,
|
|
|
- MenuView::setChildren,
|
|
|
- (Predicate<MenuView>) n ->
|
|
|
- StringUtils.isEmpty(n.getParentId())
|
|
|
- || "-1".equals(n.getParentId())));
|
|
|
- }
|
|
|
-
|
|
|
- private static Collection<MenuView> convertMenuView(Map<String, MenuEntity> menuMap,
|
|
|
- Predicate<MenuEntity> menuPredicate,
|
|
|
- Predicate<MenuButtonInfo> buttonPredicate) {
|
|
|
- Map<String, MenuEntity> group = new HashMap<>();
|
|
|
- for (MenuEntity menu : menuMap.values()) {
|
|
|
- if (group.containsKey(menu.getId())) {
|
|
|
- continue;
|
|
|
- }
|
|
|
- if (menuPredicate.test(menu)) {
|
|
|
- String parentId = menu.getParentId();
|
|
|
- MenuEntity parent;
|
|
|
- group.put(menu.getId(), menu);
|
|
|
- //有子菜单默认就有父菜单
|
|
|
- while (!StringUtils.isEmpty(parentId)) {
|
|
|
- parent = menuMap.get(parentId);
|
|
|
- if (parent == null) {
|
|
|
- break;
|
|
|
- }
|
|
|
- parentId = parent.getParentId();
|
|
|
- group.put(parent.getId(), parent);
|
|
|
- }
|
|
|
- }
|
|
|
- }
|
|
|
- return group
|
|
|
- .values()
|
|
|
- .stream()
|
|
|
- .map(menu -> MenuView.of(menu.copy(buttonPredicate)))
|
|
|
- .sorted()
|
|
|
- .collect(Collectors.toList());
|
|
|
+ .flatMapIterable(list -> TreeUtils.list2tree(list, MenuView::getId, MenuView::getParentId, MenuView::setChildren));
|
|
|
+ }
|
|
|
+
|
|
|
+ @Getter
|
|
|
+ @Setter
|
|
|
+ @AllArgsConstructor(staticName = "of")
|
|
|
+ @NoArgsConstructor
|
|
|
+ public static class AssetTypeView {
|
|
|
+ private String id;
|
|
|
+ private String name;
|
|
|
}
|
|
|
|
|
|
}
|