Skip to content

Commit e67d972

Browse files
committed
feat: add privileged mode detection and execution support
1 parent bfc5d13 commit e67d972

File tree

9 files changed

+118
-29
lines changed

9 files changed

+118
-29
lines changed

bridge/bridge.go

Lines changed: 19 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -23,14 +23,15 @@ import (
2323
var Config = &AppConfig{}
2424

2525
var Env = &EnvResult{
26-
IsStartup: true,
27-
FromTaskSch: false,
28-
WebviewPath: "",
29-
AppName: "",
30-
AppVersion: "v1.18.0",
31-
BasePath: "",
32-
OS: sysruntime.GOOS,
33-
ARCH: sysruntime.GOARCH,
26+
IsStartup: true,
27+
FromTaskSch: false,
28+
WebviewPath: "",
29+
AppName: "",
30+
AppVersion: "v1.18.0",
31+
BasePath: "",
32+
OS: sysruntime.GOOS,
33+
ARCH: sysruntime.GOARCH,
34+
IsPrivileged: false,
3435
}
3536

3637
// NewApp creates a new App application struct
@@ -53,6 +54,10 @@ func CreateApp(fs embed.FS) *App {
5354
Env.FromTaskSch = true
5455
}
5556

57+
if priv, err := IsPrivileged(); err == nil {
58+
Env.IsPrivileged = priv
59+
}
60+
5661
app := NewApp()
5762

5863
if Env.OS == "darwin" {
@@ -96,11 +101,12 @@ func (a *App) RestartApp() FlagResult {
96101

97102
func (a *App) GetEnv() EnvResult {
98103
return EnvResult{
99-
AppName: Env.AppName,
100-
AppVersion: Env.AppVersion,
101-
BasePath: Env.BasePath,
102-
OS: Env.OS,
103-
ARCH: Env.ARCH,
104+
AppName: Env.AppName,
105+
AppVersion: Env.AppVersion,
106+
BasePath: Env.BasePath,
107+
OS: Env.OS,
108+
ARCH: Env.ARCH,
109+
IsPrivileged: Env.IsPrivileged,
104110
}
105111
}
106112

bridge/exec.go

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -35,9 +35,6 @@ func (a *App) Exec(path string, args []string, options ExecOptions) FlagResult {
3535
}
3636

3737
out, err := cmd.CombinedOutput()
38-
if err != nil {
39-
return FlagResult{false, err.Error()}
40-
}
4138

4239
var output string
4340
if options.Convert {
@@ -46,6 +43,10 @@ func (a *App) Exec(path string, args []string, options ExecOptions) FlagResult {
4643
output = string(out)
4744
}
4845

46+
if err != nil {
47+
return FlagResult{false, string(output)}
48+
}
49+
4950
return FlagResult{true, output}
5051
}
5152

bridge/exec_others.go

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -35,3 +35,7 @@ func IsProcessAlive(p *os.Process) (bool, error) {
3535
}
3636
return false, fmt.Errorf("failed to check process %d: %w", p.Pid, err)
3737
}
38+
39+
func IsPrivileged() (bool, error) {
40+
return os.Geteuid() == 0, nil
41+
}

bridge/exec_windows.go

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,15 +7,18 @@ import (
77
"os"
88
"os/exec"
99
"syscall"
10+
"unsafe"
1011

1112
"golang.org/x/sys/windows"
1213
)
1314

1415
const ATTACH_PARENT_PROCESS uintptr = ^uintptr(0)
1516

1617
var (
18+
modAdvapi32 = windows.NewLazySystemDLL("advapi32.dll")
1719
modKernel32 = windows.NewLazySystemDLL("kernel32.dll")
1820

21+
procCheckTokenMembership = modAdvapi32.NewProc("CheckTokenMembership")
1922
procFreeConsole = modKernel32.NewProc("FreeConsole")
2023
procAttachConsole = modKernel32.NewProc("AttachConsole")
2124
procSetConsoleCtrlHandler = modKernel32.NewProc("SetConsoleCtrlHandler")
@@ -76,3 +79,20 @@ func IsProcessAlive(p *os.Process) (bool, error) {
7679
return false, fmt.Errorf("unexpected WaitForSingleObject status: %d", s)
7780
}
7881
}
82+
83+
func IsPrivileged() (bool, error) {
84+
var sid *windows.SID
85+
sid, err := windows.CreateWellKnownSid(windows.WinBuiltinAdministratorsSid)
86+
if err != nil {
87+
return false, err
88+
}
89+
90+
var isMember int32
91+
92+
ret, _, err := procCheckTokenMembership.Call(0, uintptr(unsafe.Pointer(sid)), uintptr(unsafe.Pointer(&isMember)))
93+
if ret == 0 {
94+
return false, err
95+
}
96+
97+
return isMember != 0, nil
98+
}

bridge/types.go

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -14,14 +14,15 @@ type App struct {
1414
}
1515

1616
type EnvResult struct {
17-
IsStartup bool `json:"-"`
18-
FromTaskSch bool `json:"-"`
19-
WebviewPath string `json:"-"`
20-
AppName string `json:"appName"`
21-
AppVersion string `json:"appVersion"`
22-
BasePath string `json:"basePath"`
23-
OS string `json:"os"`
24-
ARCH string `json:"arch"`
17+
IsStartup bool `json:"-"`
18+
FromTaskSch bool `json:"-"`
19+
WebviewPath string `json:"-"`
20+
AppName string `json:"appName"`
21+
AppVersion string `json:"appVersion"`
22+
BasePath string `json:"basePath"`
23+
OS string `json:"os"`
24+
ARCH string `json:"arch"`
25+
IsPrivileged bool `json:"isPrivileged"`
2526
}
2627

2728
type RequestOptions struct {

frontend/src/bridge/wailsjs/go/models.ts

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ export namespace bridge {
66
basePath: string;
77
os: string;
88
arch: string;
9+
isPrivileged: boolean;
910

1011
static createFrom(source: any = {}) {
1112
return new EnvResult(source);
@@ -18,6 +19,7 @@ export namespace bridge {
1819
this.basePath = source["basePath"];
1920
this.os = source["os"];
2021
this.arch = source["arch"];
22+
this.isPrivileged = source["isPrivileged"];
2123
}
2224
}
2325
export class ExecOptions {

frontend/src/stores/env.ts

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ export const useEnvStore = defineStore('env', () => {
1717
appPath: '',
1818
os: '',
1919
arch: '',
20+
isPrivileged: false,
2021
})
2122

2223
const systemProxy = ref(false)

frontend/src/utils/helper.ts

Lines changed: 32 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,31 @@ export const GrantTUNPermission = async (path: string) => {
7474
}
7575
}
7676

77+
export const RunWithPowerShell = async (
78+
path: string,
79+
args: string[] = [],
80+
options: { admin?: boolean; hidden?: boolean; wait?: boolean },
81+
) => {
82+
const { admin = false, hidden = false, wait = true, ...others } = options
83+
const psArgs: string[] = []
84+
let command = `Start-Process -FilePath "${path}"`
85+
if (args.length > 0) {
86+
const argList = args.map((a) => `"${a.replace(/"/g, '""')}"`).join(',')
87+
command += ` -ArgumentList ${argList}`
88+
}
89+
if (admin) {
90+
command += ' -Verb RunAs'
91+
}
92+
if (hidden) {
93+
command += ' -WindowStyle Hidden'
94+
}
95+
if (wait) {
96+
command += ' -Wait'
97+
}
98+
psArgs.push('-NoProfile', '-Command', command)
99+
await Exec('powershell', psArgs, { Convert: true, ...others })
100+
}
101+
77102
// SystemProxy Helper
78103
export const SetSystemProxy = async (
79104
enable: boolean,
@@ -531,11 +556,16 @@ export const QuerySchTask = async (taskName: string) => {
531556
}
532557

533558
export const CreateSchTask = async (taskName: string, xmlPath: string) => {
534-
await Exec('SchTasks', ['/Create', '/F', '/TN', taskName, '/XML', xmlPath], { Convert: true })
559+
const fn = useEnvStore().env.isPrivileged ? Exec : RunWithPowerShell
560+
await fn('SchTasks', ['/Create', '/F', '/TN', taskName, '/XML', xmlPath], {
561+
admin: true,
562+
hidden: true,
563+
})
535564
}
536565

537566
export const DeleteSchTask = async (taskName: string) => {
538-
await Exec('SchTasks', ['/Delete', '/F', '/TN', taskName], { Convert: true })
567+
const fn = useEnvStore().env.isPrivileged ? Exec : RunWithPowerShell
568+
await fn('SchTasks', ['/Delete', '/F', '/TN', taskName], { admin: true, hidden: true })
539569
}
540570

541571
// Others

frontend/src/views/SettingsView/components/components/BehaviorSettings.vue

Lines changed: 27 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,18 +1,20 @@
11
<script lang="ts" setup>
22
import { ref } from 'vue'
33
4-
import { WriteFile, RemoveFile, AbsolutePath } from '@/bridge'
4+
import { WriteFile, RemoveFile, AbsolutePath, ExitApp } from '@/bridge'
55
import { WebviewGpuPolicyOptions, WindowStateOptions } from '@/constant/app'
66
import { useAppSettingsStore, useEnvStore } from '@/stores'
77
import {
88
APP_TITLE,
99
getTaskSchXmlString,
10+
confirm,
1011
message,
1112
QuerySchTask,
1213
CreateSchTask,
1314
DeleteSchTask,
1415
CheckPermissions,
1516
SwitchPermissions,
17+
RunWithPowerShell,
1618
} from '@/utils'
1719
1820
const appSettings = useAppSettingsStore()
@@ -21,10 +23,22 @@ const envStore = useEnvStore()
2123
const isAdmin = ref(false)
2224
const isTaskScheduled = ref(false)
2325
26+
const restartApp = async (admin = false) => {
27+
if (admin) {
28+
await RunWithPowerShell(envStore.env.appPath, [], { admin, wait: false })
29+
} else {
30+
await RunWithPowerShell('explorer', [envStore.env.appPath], { wait: false })
31+
}
32+
await ExitApp()
33+
}
34+
2435
const onPermChange = async (v: boolean) => {
2536
try {
2637
await SwitchPermissions(v)
27-
message.success('common.success')
38+
if (v !== envStore.env.isPrivileged) {
39+
const ok = await confirm('Notice', 'Restart the application now?').catch(() => 0)
40+
ok && (await restartApp(v))
41+
}
2842
} catch (error: any) {
2943
message.error(error)
3044
console.log(error)
@@ -93,7 +107,17 @@ if (envStore.env.os === 'windows') {
93107
{{ $t('settings.admin') }}
94108
<span class="font-normal text-12">({{ $t('settings.needRestart') }})</span>
95109
</div>
96-
<Switch v-model="isAdmin" @change="onPermChange" />
110+
<div class="flex items-center gap-4">
111+
<Button
112+
v-if="envStore.env.isPrivileged !== isAdmin"
113+
v-tips="'titlebar.restart'"
114+
type="primary"
115+
icon="refresh"
116+
size="small"
117+
@click="() => restartApp(isAdmin)"
118+
/>
119+
<Switch v-model="isAdmin" @change="onPermChange" />
120+
</div>
97121
</div>
98122
<div v-platform="['windows']" class="px-8 py-12 flex items-center justify-between">
99123
<div class="text-16 font-bold">

0 commit comments

Comments
 (0)