Skip to content

Commit

Permalink
feat(): support block pages by license blacklist
Browse files Browse the repository at this point in the history
  • Loading branch information
weareoutman committed Jan 9, 2025
1 parent 8e258c1 commit 0f84de8
Show file tree
Hide file tree
Showing 18 changed files with 282 additions and 60 deletions.
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
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);
}

/**
* 判断一个 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));
}
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
4 changes: 2 additions & 2 deletions packages/easyops-runtime/src/menu/interfaces.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ import type {
__secret_internals,
MatchOptions,
} from "@next-core/runtime";
import type { LocationDescriptor } from "history";
import {
symbolAppId,
symbolMenuI18nNamespace,
Expand Down Expand Up @@ -61,8 +62,7 @@ export interface SidebarMenuSimpleItem {
text: string;

/** 菜单项对应的系统内地址。 */
// to?: LocationDescriptor;
to?: unknown;
to?: LocationDescriptor;

/** 菜单项对应的外部链接地址。 */
href?: string;
Expand Down
2 changes: 1 addition & 1 deletion packages/runtime/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -62,7 +62,7 @@
},
"devDependencies": {
"@microsoft/api-extractor": "^7.47.9",
"@next-api-sdk/api-gateway-sdk": "^1.1.0",
"@next-api-sdk/api-gateway-sdk": "^1.2.2",
"@next-api-sdk/micro-app-sdk": "^1.2.1",
"@next-core/build-next-libs": "^1.0.21",
"@next-core/test-next": "^1.1.7"
Expand Down
Loading

0 comments on commit 0f84de8

Please sign in to comment.