Skip to content

Commit 600968c

Browse files
committed
Added run as administrator pull-down to the permissions tab of the GUI
1 parent d79cd50 commit 600968c

File tree

2 files changed

+175
-12
lines changed

2 files changed

+175
-12
lines changed

cli/src/main/java/ca/weblite/jdeploy/gui/tabs/PermissionsPanel.java

Lines changed: 80 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,9 @@ public class PermissionsPanel extends JPanel {
2323
private final Map<PermissionRequest, JCheckBox> permissionCheckboxes;
2424
private final Map<PermissionRequest, JTextField> descriptionFields;
2525
private ActionListener changeListener;
26+
27+
// Run as Administrator controls
28+
private JComboBox<String> runAsAdministratorComboBox;
2629

2730
public PermissionsPanel() {
2831
this.permissionService = new PermissionRequestService();
@@ -72,8 +75,49 @@ private void initializeUI() {
7275

7376
add(scrollPane, BorderLayout.CENTER);
7477
}
75-
78+
79+
private void createRunAsAdministratorSection(JPanel parent) {
80+
JPanel section = new JPanel();
81+
section.setLayout(new BoxLayout(section, BoxLayout.Y_AXIS));
82+
section.setBorder(new TitledBorder("Run as Administrator"));
83+
section.setAlignmentX(Component.LEFT_ALIGNMENT);
84+
85+
JPanel settingPanel = new JPanel(new FlowLayout(FlowLayout.LEFT));
86+
settingPanel.setAlignmentX(Component.LEFT_ALIGNMENT);
87+
88+
JLabel label = new JLabel("Privilege Escalation:");
89+
runAsAdministratorComboBox = new JComboBox<>(new String[]{
90+
"disabled", "allowed", "required"
91+
});
92+
runAsAdministratorComboBox.setSelectedItem("disabled");
93+
runAsAdministratorComboBox.addActionListener(e -> fireChangeEvent());
94+
95+
settingPanel.add(label);
96+
settingPanel.add(Box.createHorizontalStrut(5));
97+
settingPanel.add(runAsAdministratorComboBox);
98+
99+
JLabel descriptionLabel = new JLabel(
100+
"<html><body style='width: 600px'>" +
101+
"<b>disabled</b>: Application runs with normal user privileges (default)<br/>" +
102+
"<b>allowed</b>: Creates both normal and \"Run as administrator\" launchers<br/>" +
103+
"<b>required</b>: All launchers require administrator privileges" +
104+
"</body></html>"
105+
);
106+
descriptionLabel.setFont(descriptionLabel.getFont().deriveFont(11f));
107+
descriptionLabel.setForeground(Color.GRAY);
108+
descriptionLabel.setBorder(new EmptyBorder(5, 10, 10, 10));
109+
110+
section.add(settingPanel);
111+
section.add(descriptionLabel);
112+
113+
parent.add(section);
114+
parent.add(Box.createVerticalStrut(15));
115+
}
116+
76117
private void createPermissionSections(JPanel parent) {
118+
// Add Run as Administrator section at the top
119+
createRunAsAdministratorSection(parent);
120+
77121
// Group permissions by category
78122
createPermissionGroup(parent, "Media Access", new PermissionRequest[]{
79123
PermissionRequest.CAMERA,
@@ -248,34 +292,42 @@ private String generateGenericDescription(PermissionRequest permission) {
248292
*/
249293
public void loadPermissions(JSONObject packageJson) {
250294
Map<PermissionRequest, String> permissions = permissionService.getPermissionRequests(packageJson);
251-
295+
296+
// Load run as administrator setting
297+
if (packageJson.has("jdeploy") && packageJson.getJSONObject("jdeploy").has("runAsAdministrator")) {
298+
String runAsAdmin = packageJson.getJSONObject("jdeploy").getString("runAsAdministrator");
299+
runAsAdministratorComboBox.setSelectedItem(runAsAdmin);
300+
} else {
301+
runAsAdministratorComboBox.setSelectedItem("disabled");
302+
}
303+
252304
// Clear all checkboxes and descriptions, hide all description panels
253305
permissionCheckboxes.values().forEach(cb -> cb.setSelected(false));
254306
descriptionFields.values().forEach(field -> {
255307
field.setText("");
256308
field.getParent().setVisible(false); // Hide description panel
257309
});
258-
310+
259311
// Set loaded permissions
260312
for (Map.Entry<PermissionRequest, String> entry : permissions.entrySet()) {
261313
PermissionRequest permission = entry.getKey();
262314
String description = entry.getValue();
263-
315+
264316
JCheckBox checkbox = permissionCheckboxes.get(permission);
265317
JTextField descField = descriptionFields.get(permission);
266-
318+
267319
if (checkbox != null && descField != null) {
268320
checkbox.setSelected(true);
269321
descField.getParent().setVisible(true); // Show description panel
270-
322+
271323
// Only set description if it's not the generic one
272324
String genericDesc = generateGenericDescription(permission);
273325
if (!genericDesc.equals(description)) {
274326
descField.setText(description);
275327
}
276328
}
277329
}
278-
330+
279331
// Refresh the UI
280332
revalidate();
281333
repaint();
@@ -286,25 +338,41 @@ public void loadPermissions(JSONObject packageJson) {
286338
*/
287339
public void savePermissions(JSONObject packageJson) {
288340
Map<PermissionRequest, String> permissions = new HashMap<>();
289-
341+
290342
for (Map.Entry<PermissionRequest, JCheckBox> entry : permissionCheckboxes.entrySet()) {
291343
PermissionRequest permission = entry.getKey();
292344
JCheckBox checkbox = entry.getValue();
293-
345+
294346
if (checkbox.isSelected()) {
295347
JTextField descField = descriptionFields.get(permission);
296348
String description = descField != null ? descField.getText().trim() : "";
297-
349+
298350
// If description is empty, use generic description
299351
if (description.isEmpty()) {
300352
description = generateGenericDescription(permission);
301353
}
302-
354+
303355
permissions.put(permission, description);
304356
}
305357
}
306-
358+
307359
permissionService.savePermissionRequests(packageJson, permissions);
360+
361+
// Save run as administrator setting
362+
if (!packageJson.has("jdeploy")) {
363+
packageJson.put("jdeploy", new JSONObject());
364+
}
365+
JSONObject jdeployObj = packageJson.getJSONObject("jdeploy");
366+
String runAsAdmin = (String) runAsAdministratorComboBox.getSelectedItem();
367+
368+
// Only save if not default value
369+
if ("disabled".equals(runAsAdmin)) {
370+
if (jdeployObj.has("runAsAdministrator")) {
371+
jdeployObj.remove("runAsAdministrator");
372+
}
373+
} else {
374+
jdeployObj.put("runAsAdministrator", runAsAdmin);
375+
}
308376
}
309377

310378
/**
Lines changed: 95 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,95 @@
1+
# Run as Administrator Feature
2+
3+
## Overview
4+
5+
The "Run as Administrator" feature allows jDeploy applications to be configured to run with elevated privileges on Windows, macOS, and Linux platforms. This feature provides developers with control over privilege escalation requirements for their applications.
6+
7+
## Configuration
8+
9+
The feature is configured via the `jdeploy.runAsAdministrator` property in the application's `package.json`:
10+
11+
```json
12+
{
13+
"jdeploy": {
14+
"runAsAdministrator": "allowed"
15+
}
16+
}
17+
```
18+
19+
## Supported Values
20+
21+
### `"disabled"` (Default)
22+
- Applications run with normal user privileges
23+
- No elevated launchers are generated
24+
- This is the default behavior when the property is not specified
25+
26+
### `"allowed"`
27+
- Applications can optionally run with elevated privileges
28+
- The installer generates two launcher variants for each app:
29+
- Standard launcher (normal privileges)
30+
- "Run as administrator" launcher (elevated privileges)
31+
- Users can choose which launcher to use based on their needs
32+
33+
### `"required"`
34+
- Applications always run with elevated privileges
35+
- All generated launchers require administrator access
36+
- Standard launchers are configured to automatically request elevation
37+
38+
## Platform Implementation
39+
40+
### Windows
41+
- Uses the native "Run as administrator" option
42+
- Elevated launchers are configured to trigger Windows UAC prompts
43+
- Leverages Windows built-in privilege escalation mechanisms
44+
45+
### macOS
46+
- Creates a wrapper launcher app that launches the main application with elevated permissions
47+
- Utilizes macOS authorization services through the wrapper
48+
- May prompt for administrator password when elevated launchers are used
49+
50+
### Linux
51+
- Creates desktop file entries that use `pkexec` to launch the application
52+
- `pkexec` provides PolicyKit-based privilege escalation
53+
- Follows Linux security guidelines for elevated application execution
54+
55+
## Use Cases
56+
57+
### Development Tools
58+
Applications that need to modify system files, install drivers, or access protected system resources.
59+
60+
### System Utilities
61+
Administrative tools that require elevated access to perform system maintenance or configuration tasks.
62+
63+
### Optional Elevated Features
64+
Applications with some features that benefit from elevated access but can function normally without it.
65+
66+
## Security Considerations
67+
68+
- Applications should follow the principle of least privilege
69+
- Only request elevation when absolutely necessary
70+
- Users should be clearly informed when elevated privileges are required
71+
- Applications should validate the need for elevation before requesting it
72+
73+
## Installation Behavior
74+
75+
When `runAsAdministrator` is set to `"allowed"`:
76+
- The installer creates both standard and elevated launcher shortcuts
77+
- Elevated launchers are clearly labeled (e.g., "MyApp (Run as Administrator)")
78+
- Users can access both variants from the start menu or applications folder
79+
80+
When `runAsAdministrator` is set to `"required"`:
81+
- All launchers are configured for elevation
82+
- Users will see elevation prompts when launching the application
83+
- No standard (non-elevated) launchers are created
84+
85+
## Backward Compatibility
86+
87+
- Existing applications without the `runAsAdministrator` property maintain current behavior
88+
- Default value of `"disabled"` ensures no breaking changes
89+
- Applications can opt-in to elevated privileges as needed
90+
91+
## Future Enhancements
92+
93+
- Fine-grained permission control for specific application features
94+
- Integration with platform-specific permission systems
95+
- Runtime privilege escalation for specific operations

0 commit comments

Comments
 (0)