Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(): support block pages by license blacklist #4591

Merged
merged 2 commits into from
Jan 10, 2025
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
10 changes: 5 additions & 5 deletions bricks/e2e/src/list-by-use-brick/index.tsx
Original file line number Diff line number Diff line change
@@ -1,8 +1,8 @@
import React from "react";
import { createDecorators } from "@next-core/element";
import { ReactNextElement } from "@next-core/react-element";
import { ReactUseBrick } from "@next-core/react-runtime";
import { UseSingleBrickConf } from "@next-core/types";
import { ReactUseMultipleBricks } from "@next-core/react-runtime";
import { UseBrickConf } from "@next-core/types";

const { defineElement, property } = createDecorators();

Expand All @@ -13,7 +13,7 @@ export
})
class ListByUseBrick extends ReactNextElement {
@property({ attribute: false })
accessor useBrick: UseSingleBrickConf;
accessor useBrick: UseBrickConf;

@property({ attribute: false })
accessor data: unknown;
Expand All @@ -29,7 +29,7 @@ export function ListByUseBrickComponent({
useBrick,
data,
}: {
useBrick: UseSingleBrickConf;
useBrick: UseBrickConf;
data: unknown;
}) {
if (!useBrick || !Array.isArray(data)) {
Expand All @@ -38,7 +38,7 @@ export function ListByUseBrickComponent({
return (
<>
{data.map((datum, index) => (
<ReactUseBrick useBrick={useBrick} data={datum} key={index} />
<ReactUseMultipleBricks useBrick={useBrick} data={datum} key={index} />
))}
</>
);
Expand Down
5 changes: 3 additions & 2 deletions etc/runtime.api.md
Original file line number Diff line number Diff line change
Expand Up @@ -348,7 +348,7 @@ export interface PageViewInfo {
// (undocumented)
path?: string;
// (undocumented)
status: "ok" | "failed" | "redirected" | "not-found";
status: "ok" | "failed" | "redirected" | "not-found" | "blocked";
}

// @public (undocumented)
Expand Down Expand Up @@ -441,6 +441,7 @@ export interface RuntimeHooks {
isLoggedIn(): boolean;
authenticate?(...args: unknown[]): unknown;
logout?(...args: unknown[]): unknown;
isBlockedPath?(pathname: string): boolean;
};
// (undocumented)
checkInstalledApps?: {
Expand Down Expand Up @@ -586,7 +587,7 @@ function updateTemplatePreviewSettings(appId: string, templateId: string, settin
// dist/types/Dialog.d.ts:10:5 - (ae-forgotten-export) The symbol "show_2" needs to be exported by the entry point index.d.ts
// dist/types/Notification.d.ts:8:5 - (ae-forgotten-export) The symbol "show" needs to be exported by the entry point index.d.ts
// dist/types/StoryboardFunctionRegistry.d.ts:48:5 - (ae-forgotten-export) The symbol "FunctionCoverageSettings" needs to be exported by the entry point index.d.ts
// dist/types/internal/Runtime.d.ts:34:9 - (ae-forgotten-export) The symbol "AppForCheck" needs to be exported by the entry point index.d.ts
// dist/types/internal/Runtime.d.ts:35:9 - (ae-forgotten-export) The symbol "AppForCheck" needs to be exported by the entry point index.d.ts

// (No @packageDocumentation comment for this package)

Expand Down
7 changes: 3 additions & 4 deletions packages/babel-preset-next/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -19,15 +19,14 @@ module.exports = () => {
: env === "commonjs"
? {
targets: {
// Fallback to versions that doesn't support nullish coalescing
node: "12",
node: "14",
},
}
: {
modules: false,
useBuiltIns: "entry",
corejs: {
version: "3.37",
version: "3.38",
},
},
],
Expand All @@ -50,7 +49,7 @@ module.exports = () => {
transformRuntime,
{
// https://github.com/babel/babel/issues/9454#issuecomment-460425922
version: "7.24.5",
version: "7.25.6",
},
],
],
Expand Down
2 changes: 1 addition & 1 deletion packages/brick-container/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,7 @@
"ws": "^8.18.0"
},
"devDependencies": {
"@next-api-sdk/api-gateway-sdk": "^1.1.0",
"@next-api-sdk/api-gateway-sdk": "^1.2.2",
"@next-api-sdk/micro-app-standalone-sdk": "^1.1.0",
"@next-core/build-next-bricks": "^1.23.7",
"@next-core/easyops-runtime": "^0.12.46",
Expand Down
2 changes: 1 addition & 1 deletion packages/easyops-runtime/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@
"test:ci": "cross-env NODE_ENV='test' CI=true test-next"
},
"dependencies": {
"@next-api-sdk/api-gateway-sdk": "^1.2.0",
"@next-api-sdk/api-gateway-sdk": "^1.2.2",
"@next-api-sdk/cmdb-sdk": "^1.1.0",
"@next-api-sdk/micro-app-sdk": "^1.2.1",
"@next-api-sdk/micro-app-standalone-sdk": "^1.1.0",
Expand Down
10 changes: 9 additions & 1 deletion packages/easyops-runtime/src/auth-v2.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,15 @@ export function authV2Factory() {
const v2Kit = getV2RuntimeFromDll();
if (v2Kit) {
return Object.freeze(
pick(v2Kit, ["authenticate", "getAuth", "isLoggedIn", "logout"])
pick(v2Kit, [
"authenticate",
"getAuth",
"isLoggedIn",
"logout",
"isBlockedPath",
"isBlockedUrl",
"isBlockedHref",
])
) as typeof auth;
}
}
62 changes: 61 additions & 1 deletion packages/easyops-runtime/src/auth.spec.ts
Original file line number Diff line number Diff line change
@@ -1,9 +1,26 @@
import { authenticate, getAuth, logout, isLoggedIn } from "./auth.js";
import {
authenticate,
getAuth,
logout,
isLoggedIn,
isBlockedPath,
isBlockedHref,
isBlockedUrl,
} from "./auth.js";
// import { resetPermissionPreChecks } from "./internal/checkPermissions.js";

// jest.mock("./internal/checkPermissions");

describe("auth", () => {
const base = document.createElement("base");
beforeAll(() => {
base.setAttribute("href", "/next/");
document.head.appendChild(base);
});
afterAll(() => {
document.head.removeChild(base);
});

it("should work", () => {
expect(getAuth()).toEqual({});
expect(isLoggedIn()).toEqual(false);
Expand All @@ -12,15 +29,58 @@ describe("auth", () => {
username: "mock-user",
userInstanceId: "abc",
accessRule: "cmdb",
license: {
blackList: ["/a", "/b/:id/c"],
},
});
expect(getAuth()).toEqual({
org: 8888,
username: "mock-user",
userInstanceId: "abc",
accessRule: "cmdb",
license: {
blackList: ["/a", "/b/:id/c"],
},
});
expect(isLoggedIn()).toEqual(true);

expect(isBlockedPath("/a")).toEqual(true);
expect(isBlockedPath("/a/123")).toEqual(true);
expect(isBlockedPath("/b")).toEqual(false);
expect(isBlockedPath("/b/123")).toEqual(false);
expect(isBlockedPath("/b/123/c")).toEqual(true);
expect(isBlockedPath("/b/123/c/d")).toEqual(true);
expect(isBlockedPath("/b/123/x")).toEqual(false);
expect(isBlockedPath("/c")).toEqual(false);

expect(isBlockedHref("/a")).toEqual(false);
expect(isBlockedHref("/next/a")).toEqual(true);
expect(isBlockedHref("a")).toEqual(true);
expect(isBlockedHref("http://localhost/a")).toEqual(false);
expect(isBlockedHref("http://localhost/next/a")).toEqual(true);
expect(isBlockedHref("http://example.com/a")).toEqual(false);
expect(isBlockedHref("http://example.com/next/a")).toEqual(false);

expect(isBlockedUrl("/a?q=1")).toEqual(true);
expect(isBlockedUrl("/next/a?q=1")).toEqual(false);
expect(
isBlockedUrl({
pathname: "/a",
search: "?q=1",
})
).toEqual(true);
expect(
isBlockedUrl({
pathname: "/next/a",
search: "?q=1",
})
).toEqual(false);
expect(
isBlockedUrl({
search: "?q=1",
})
).toEqual(false);

// expect(resetPermissionPreChecks).not.toBeCalled();
logout();
expect(getAuth()).toEqual({});
Expand Down
37 changes: 37 additions & 0 deletions packages/easyops-runtime/src/auth.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
import { getBasePath, matchPath } from "@next-core/runtime";
import type { AuthApi_CheckLoginResponseBody } from "@next-api-sdk/api-gateway-sdk";
import { createLocation, type LocationDescriptor } from "history";
import { resetPermissionPreChecks } from "./checkPermissions.js";

const auth: AuthInfo = {};
Expand Down Expand Up @@ -49,3 +51,38 @@ export function logout(): void {
export function isLoggedIn(): boolean {
return auth.username !== undefined;
}

/**
* 判断一个内部 URL 路径是否被屏蔽。
*/
export function isBlockedPath(pathname: string): boolean {
return !!auth.license?.blackList?.some((path) =>
matchPath(pathname, { path })
);
}

/**
* 判断一个内部 URL 是否被屏蔽。
*/
export function isBlockedUrl(url: string | LocationDescriptor): boolean {
const pathname = (typeof url === "string" ? createLocation(url) : url)
.pathname;
if (typeof pathname !== "string") {
return false;
}
return isBlockedPath(pathname);
}
weareoutman marked this conversation as resolved.
Show resolved Hide resolved

/**
* 判断一个 href 是否被屏蔽。
*/
export function isBlockedHref(href: string): boolean {
const basePath = getBasePath();
const url = new URL(href, `${location.origin}${basePath}`);
// 忽略外链地址
if (url.origin !== location.origin || !url.pathname.startsWith(basePath)) {
return false;
}
// 转换为内部路径
return isBlockedPath(url.pathname.substring(basePath.length - 1));
}
weareoutman marked this conversation as resolved.
Show resolved Hide resolved
19 changes: 18 additions & 1 deletion packages/easyops-runtime/src/menu/fetchMenuById.spec.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ import { InstalledMicroAppApi_getMenusInfo } from "@next-api-sdk/micro-app-sdk";
import { createProviderClass } from "@next-core/utils/general";
import { __test_only, createRuntime } from "@next-core/runtime";
import { YAMLException } from "js-yaml";
import type { LocationDescriptor } from "history";
import { fetchMenuById, getMenuById } from "./fetchMenuById.js";
import type { RuntimeContext, RuntimeHelpers } from "./interfaces.js";
import * as auth from "../auth.js";
Expand All @@ -32,6 +33,14 @@ jest.mock("../auth.js", () => ({
isAdmin() {
return false;
},
isBlockedUrl(url: LocationDescriptor) {
return typeof url === "string"
? url.includes("blocked")
: url.pathname?.includes("blocked");
},
isBlockedHref(href: string) {
return href.includes("blocked");
},
}));

createRuntime({
Expand Down Expand Up @@ -91,6 +100,14 @@ const menuList = [
text: "Menu Item 6",
to: '/${ APP.unknown = ["next","test"] | join : "/" }',
},
{
text: "Menu Item blocked by to",
to: "/to/blocked",
},
{
text: "Menu Item blocked by href",
href: "/href/blocked",
},
{
text: "Menu Item 7",
children: [
Expand Down Expand Up @@ -211,7 +228,7 @@ const menuList = [

(
InstanceApi_postSearch as jest.Mock<typeof InstanceApi_postSearch>
).mockImplementation(async (objectId, data: any) => {
).mockImplementation(async (_objectId, data: any) => {
return {
list: menuList.filter((menu) => menu.menuId === data.query.menuId.$eq),
};
Expand Down
55 changes: 35 additions & 20 deletions packages/easyops-runtime/src/menu/fetchMenuById.ts
Original file line number Diff line number Diff line change
Expand Up @@ -11,11 +11,13 @@ import type {
RuntimeMenuItemRawData,
SidebarMenu,
SidebarMenuItem,
SidebarMenuSimpleItem,
} from "./interfaces.js";
import { computeMenuItems, computeMenuData } from "./computeMenuData.js";
import { fetchMenuTitle } from "./fetchMenuTitle.js";
import { getMenusOfStandaloneApp } from "./getMenusOfStandaloneApp.js";
import { preCheckPermissionsForAny } from "../checkPermissions.js";
import { isBlockedHref, isBlockedUrl } from "../auth.js";

const menuPromises = new Map<string, Promise<void>>();

Expand All @@ -24,30 +26,43 @@ const menuCache = new Map<string, SidebarMenu>();
function transformMenuItems(
menuItems: RuntimeMenuItemRawData[] | undefined
): SidebarMenuItem[] | undefined {
return menuItems?.filter(checkIfOfComputed).map((item) => {
const children = transformMenuItems(item.children);
const transformedMenuItem: SidebarMenuItem =
item.type === "group"
? {
type: "group",
title: item.text,
childLayout: item.childLayout,
items: children,
groupId: item.groupId,
groupFrom: item.groupFrom,
}
: children?.length
return menuItems
?.filter(checkIfOfComputed)
.map((item) => {
const children = transformMenuItems(item.children);
const transformedMenuItem: SidebarMenuItem | null =
item.type === "group"
? {
type: "subMenu",
childLayout: item.childLayout,
type: "group",
title: item.text,
icon: item.icon,
childLayout: item.childLayout,
items: children,
defaultExpanded: item.defaultExpanded,
groupId: item.groupId,
groupFrom: item.groupFrom,
}
: (omit(item, ["type", "items", "children"]) as SidebarMenuItem);
return transformedMenuItem;
});
: children?.length
? {
type: "subMenu",
childLayout: item.childLayout,
title: item.text,
icon: item.icon,
items: children,
defaultExpanded: item.defaultExpanded,
}
: isMenuItemBlocked(item as SidebarMenuSimpleItem)
? null
: (omit(item, ["type", "items", "children"]) as SidebarMenuItem);
return transformedMenuItem;
})
.filter(Boolean) as SidebarMenuItem[];
}

function isMenuItemBlocked(item: SidebarMenuSimpleItem) {
return item.href
? isBlockedHref(item.href)
: item.to
? isBlockedUrl(item.to)
: false;
}

export function getMenuById(menuId: string) {
Expand Down
Loading
Loading