Skip to content

Commit

Permalink
feat: add open ext API for url, file, folder with scoped permission
Browse files Browse the repository at this point in the history
  • Loading branch information
HuakunShen committed Aug 9, 2024
1 parent a332573 commit 8cc789e
Show file tree
Hide file tree
Showing 16 changed files with 219 additions and 115 deletions.
2 changes: 1 addition & 1 deletion apps/cli/__tests__/verify.test.ts
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import { expect, test } from "bun:test"
import path from "path"
import { getRootDir } from "@/constants"
import { expect, test } from "bun:test"
import fs from "fs-extra"
import { verifyCmd } from "../src/commands/verify"

Expand Down
4 changes: 2 additions & 2 deletions extensions/jwt-inspector/package.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"$schema": "https://schema.kunkun.sh",
"$schema": "../../packages/schema/manifest-json-schema.json",
"name": "kunkun-ext-jwt",
"version": "0.0.2",
"private": true,
Expand Down Expand Up @@ -47,7 +47,7 @@
"format": "prettier --write ."
},
"dependencies": {
"@kksh/api": "0.0.4-alpha.1",
"@kksh/api": "workspace:*",
"@kksh/svelte": "0.1.4",
"clsx": "^2.1.1",
"cross-env": "^7.0.3",
Expand Down
1 change: 0 additions & 1 deletion extensions/jwt-inspector/src/routes/search/+page.svelte
Original file line number Diff line number Diff line change
@@ -1,5 +1,4 @@
<script lang="ts">
import { base } from '$app/paths';
import { Alert, Button, ThemeWrapper, Command } from '@kksh/svelte';
import Calendar from 'lucide-svelte/icons/calendar';
import * as jose from 'jose';
Expand Down
17 changes: 14 additions & 3 deletions extensions/qrcode/package.json
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
{
"$schema": "https://schema.kunkun.sh",
"$schema": "../../packages/schema/manifest-json-schema.json",
"name": "qrcode",
"version": "0.0.2",
"description": "Jarvis QRCode Extension",
Expand All @@ -18,7 +18,18 @@
"clipboard:read-text",
"clipboard:read-image",
"clipboard:write-text",
"shell:open"
"clipboard:write-image",
{
"permission": "open:url",
"allow": [
{
"url": "https://**"
},
{
"url": "http://**"
}
]
}
],
"customUiCmds": [
{
Expand Down Expand Up @@ -50,7 +61,7 @@
"format": "prettier --write ."
},
"dependencies": {
"@kksh/api": "0.0.4-alpha.1",
"@kksh/api": "workspace:*",
"@kksh/svelte": "0.1.4",
"clsx": "^2.1.1",
"cross-env": "^7.0.3",
Expand Down
7 changes: 4 additions & 3 deletions extensions/qrcode/src/routes/+page.svelte
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
<script lang="ts">
import { base } from '$app/paths';
import { Button, Input } from '@kksh/svelte';
import QR from '$lib/components/QR.svelte';
import QRCode from 'easyqrcodejs';
import { onMount } from 'svelte';
import { shell, clipboard, fs, dialog } from '@kksh/api/ui/iframe';
import { clipboard, fs, dialog, open } from '@kksh/api/ui/iframe';
import * as v from 'valibot';
import {
ClipboardCopyIcon,
Expand Down Expand Up @@ -105,13 +106,13 @@
<pre
on:click={() => {
if (url.startsWith('http')) {
shell.open(url);
open.openUrl(url);
}
}}
class="cursor-pointer text-wrap px-3">{url}</pre>
</div>
{/if}
<a href="./detect-qrcode.html">
<a href="{base}/detect">
<Button size="sm">
<SearchIcon class="mr-1 h-4 w-4" />
Detect QRCode From Screenshot <LinkIcon class="ml-2 w-4" /></Button
Expand Down
13 changes: 9 additions & 4 deletions extensions/qrcode/src/routes/detect/+page.svelte
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
<script lang="ts">
import jsQR from 'jsqr';
import { clipboard, shell } from '@kksh/api/ui/iframe';
import { base } from '$app/paths';
import { clipboard, open } from '@kksh/api/ui/iframe';
import { Button } from '@kksh/svelte';
import { ModeWatcher } from 'mode-watcher';
import { onMount } from 'svelte';
Expand Down Expand Up @@ -51,7 +52,7 @@
<div class=" flex grow flex-col items-center justify-center space-y-5">
<div class="flex space-x-3">
<Button on:click={readScreenshot}>Read QRCode Screenshot From Clipboard</Button>
<a href="./">
<a href={base}>
<Button>Generate QRCode <LinkIcon class="ml-2 w-4" /></Button>
</a>
</div>
Expand All @@ -60,8 +61,12 @@
href={detectedCode}
on:click={(e) => {
e.preventDefault();
shell.open(detectedCode);
}}>{detectedCode}</a
if (detectedCode.startsWith('http')) {
open.openUrl(detectedCode);
}
}}
>
{detectedCode}
</a>
</div>
</main>
9 changes: 6 additions & 3 deletions packages/api/src/models/manifest.ts
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,8 @@ import {
AllKunkunPermission,
FsPermissionScopedSchema,
KunkunFsPermissionSchema,
KunkunManifestPermission
KunkunManifestPermission,
OpenPermissionScopedSchema
} from "../ui/api/permissions"
import { Icon } from "./icon"

Expand Down Expand Up @@ -113,10 +114,12 @@ export const KunkunExtManifest = object({
name: string("Name of the extension (Human Readable)"),
shortDescription: string("Description of the extension (Will be displayed in store)"),
longDescription: string("Long description of the extension (Will be displayed in store)"),
identifier: string("Unique identifier for the extension, must be the same as extension folder name"),
identifier: string(
"Unique identifier for the extension, must be the same as extension folder name"
),
icon: Icon,
permissions: array(
union([KunkunManifestPermission, FsPermissionScopedSchema]),
union([KunkunManifestPermission, FsPermissionScopedSchema, OpenPermissionScopedSchema]),
"Permissions Declared by the extension. e.g. clipboard-all. Not declared APIs will be blocked."
),
demoImages: array(string("Demo images for the extension")),
Expand Down
10 changes: 10 additions & 0 deletions packages/api/src/ui/api/open.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
import { getDefaultClientAPI } from "tauri-api-adapter"
import { type IOpen } from "../client"
import type { IOpenServer } from "../server/open"

const defaultClientAPI = getDefaultClientAPI<IOpenServer>()
export const comlinkOpen: IOpen = {
openUrl: (url: string) => defaultClientAPI.openUrl(url),
openFile: (path: string) => defaultClientAPI.openUrl(path),
openFolder: (path: string) => defaultClientAPI.openUrl(path)
}
28 changes: 24 additions & 4 deletions packages/api/src/ui/api/permissions.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,14 +27,27 @@ export const KunkunFsPermissionSchema = union([
literal("fs:stat"),
literal("fs:search")
])
export const PermissionScopeSchema = object({ path: optional(string()), url: optional(string()) })
export type KunkunFsPermission = InferOutput<typeof KunkunFsPermissionSchema>
export const FsPermissionScopedSchema = object({
permission: KunkunFsPermissionSchema,
allow: optional(array(object({ path: string() }))),
deny: optional(array(object({ path: string() })))
allow: optional(array(PermissionScopeSchema)),
deny: optional(array(PermissionScopeSchema))
})
export type FsPermissionScoped = InferOutput<typeof FsPermissionScopedSchema>

export const OpenPermissionSchema = union([
literal("open:url"),
literal("open:file"),
literal("open:folder")
])
export const OpenPermissionScopedSchema = object({
permission: OpenPermissionSchema,
allow: optional(array(PermissionScopeSchema)),
deny: optional(array(PermissionScopeSchema))
})
export type OpenPermissionScoped = InferOutput<typeof OpenPermissionScopedSchema>

// export const FsPermissionSchema = union([
// literal("fs:allow-desktop-read-recursive"),
// literal("fs:allow-desktop-write-recursive"),
Expand Down Expand Up @@ -73,7 +86,11 @@ export const KunkunManifestPermission = union([
SystemPermissionSchema
// FsScopePermissionSchema
])
export const AllKunkunPermission = union([KunkunManifestPermission, KunkunFsPermissionSchema])
export const AllKunkunPermission = union([
KunkunManifestPermission,
KunkunFsPermissionSchema,
OpenPermissionSchema
])
export type AllKunkunPermission = InferOutput<typeof AllKunkunPermission>
export type PermissionDescriptions = Record<AllKunkunPermission, string>

Expand Down Expand Up @@ -121,7 +138,7 @@ export const permissionDescriptions: PermissionDescriptions = {
"Allows reading directories from the file system. Permissions to read files not granted, must be declared separately",
"fs:write": "Allows writing files to the file system",
"fs:exists": "Allows checking if a file exists in the file system",
"fs:stat": "Allows getting file metadata from the file system"
"fs:stat": "Allows getting file metadata from the file system",
/* ---------------------------- File System Scope --------------------------- */
// "fs:scope-download-recursive": "Allows reading files from the download directory and its subdirectories",
// "fs:allow-desktop-read-recursive":
Expand All @@ -142,4 +159,7 @@ export const permissionDescriptions: PermissionDescriptions = {
// "Allow reading files from the desktop directory and its subdirectories",
// "fs:scope-documents-recursive":
// "Allow reading files from the documents directory and its subdirectories"
"open:url": "Open URLs",
"open:file": "Open Files",
"open:folder": "Open Folders"
}
6 changes: 6 additions & 0 deletions packages/api/src/ui/client.ts
Original file line number Diff line number Diff line change
Expand Up @@ -160,3 +160,9 @@ export interface IFs {
writeTextFile: typeof writeTextFile
fileSearch: typeof fileSearch
}

export interface IOpen {
openUrl: (url: string) => Promise<void>
openFile: (path: string) => Promise<void>
openFolder: (path: string) => Promise<void>
}
1 change: 1 addition & 0 deletions packages/api/src/ui/iframe/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ export {
export { type IUiIframe } from "../client"
export { comlinkUI as ui, registerDragRegion } from "../api/iframe-ui"
export { db, constructJarvisExtDBToServerDbAPI } from "../api/db"
export { comlinkOpen as open } from "../api/open"
export { toast } from "../api/toast"
export { expose, wrap } from "@huakunshen/comlink"
export { comlinkSystem as system } from "../api/system"
Expand Down
3 changes: 2 additions & 1 deletion packages/api/src/ui/server/fs.ts
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,6 @@ export async function translateScopeToPath(scope: string): Promise<string> {
*/
async function matchPathAndScope(target: string, scope: string): Promise<boolean> {
const translatedScope = await translateScopeToPath(scope)
console.log("matchPathAndScope", target, translatedScope, minimatch(target, translatedScope))
return minimatch(target, translatedScope)
}

Expand Down Expand Up @@ -156,11 +155,13 @@ async function verifyPermission(
for (const permission of matchedPermissionScope) {
// deny has priority, if deny rule is matched, we ignore allow rule
for (const deny of permission.deny || []) {
if (!deny.path) continue
if (await matchPathAndScope(fullPath, deny.path)) {
throw new Error(`Permission denied for path: ${fullPath} by rule ${deny.path}`)
}
}
for (const allow of permission.allow || []) {
if (!allow.path) continue
if (await matchPathAndScope(fullPath, allow.path)) {
return
}
Expand Down
21 changes: 15 additions & 6 deletions packages/api/src/ui/server/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,11 +13,9 @@ import {
constructShellApi,
constructSystemInfoApi,
constructUpdownloadApi,
type AllPermission,
type ClipboardPermission,
type DialogPermission,
type FetchPermission,
type FsPermission,
type IFullAPI,
type NetworkPermission,
type NotificationPermission,
Expand All @@ -30,10 +28,12 @@ import {
AllKunkunPermission,
type FsPermissionScoped,
type KunkunFsPermission,
type OpenPermissionScoped,
type SystemPermission
} from "../api/permissions"
import type { IDbServer } from "./db"
import { constructFsApi, type IFsServer } from "./fs"
import { constructOpenApi } from "./open"
import { constructSystemApi, type ISystemServer } from "./system"
import { constructToastApi, type IToastServer } from "./toast"
import { constructIframeUiApi, type IUiIframeServer, type IUiWorkerServer } from "./ui"
Expand All @@ -59,9 +59,9 @@ function getStringPermissions(
}

function getObjectPermissions(
permissions: (AllKunkunPermission | FsPermissionScoped)[]
): FsPermissionScoped[] {
return permissions.filter((p) => typeof p !== "string") as FsPermissionScoped[]
permissions: (AllKunkunPermission | FsPermissionScoped | OpenPermissionScoped)[]
): (FsPermissionScoped | OpenPermissionScoped)[] {
return permissions.filter((p) => typeof p !== "string")
}

export function constructJarvisServerAPIWithPermissions(
Expand All @@ -81,7 +81,16 @@ export function constructJarvisServerAPIWithPermissions(
p.startsWith("notification:")
) as NotificationPermission[]
),
constructFsApi(getObjectPermissions(permissions).filter((p) => p.permission.startsWith("fs:"))),
constructFsApi(
(getObjectPermissions(permissions) as FsPermissionScoped[]).filter((p) =>
p.permission.startsWith("fs:")
)
),
constructOpenApi(
(getObjectPermissions(permissions) as OpenPermissionScoped[]).filter((p) =>
p.permission.startsWith("open:")
)
),
// constructFsApi(permissions.filter((p) => p.startsWith("fs:")) as FsPermission[]),
constructOsApi(
getStringPermissions(permissions).filter((p) => p.startsWith("os:")) as OsPermission[]
Expand Down
Loading

0 comments on commit 8cc789e

Please sign in to comment.