├── .gitignore
├── pkg
├── icon.png
├── postinstall.sh
├── blog.mostlymac.PrivilegesDemoter.conf
├── blog.mostlymac.privileges.check.plist
├── blog.mostlymac.demoteonlogin.plist
├── preinstall_Standalone.sh
├── preinstall_SAPPrivileges.sh
├── preinstall_Standalone_SwiftDialog.sh
├── preinstall_SAPPrivileges_SwiftDialog.sh
└── PrivilegesDemoter.sh
├── Screenshots
├── JamfHelper-Dark.png
├── JamfHelper-Light.png
├── SwiftDialog-Light.png
├── IBMNotifier-HelpBtn-Dark.png
├── IBMNotifier-HelpBtn-Light.png
├── SwiftDialog-HelpBtn-Dark.png
├── SwiftDialog-HelpInfo-Dark.png
├── SwiftDialog-HelpLink-Light.png
└── README.md
├── Configuration Profiles
├── Example_BackgroundServiceManagement.mobileconfig
├── Example_DockToggleTimeout.mobileconfig
└── Example_PrivilegesDemoter3.mobileconfig
├── Uninstall PrivilegesDemoter.sh
├── README.md
├── LICENSE
├── JSON Schema - PrivilegesDemoter-3.0.json
└── Demote Admin Privileges.sh
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | *.pkg
3 | *.pkgproj
4 | pkg/build/*
5 | *.app
6 |
--------------------------------------------------------------------------------
/pkg/icon.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sgmills/PrivilegesDemoter/HEAD/pkg/icon.png
--------------------------------------------------------------------------------
/Screenshots/JamfHelper-Dark.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sgmills/PrivilegesDemoter/HEAD/Screenshots/JamfHelper-Dark.png
--------------------------------------------------------------------------------
/Screenshots/JamfHelper-Light.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sgmills/PrivilegesDemoter/HEAD/Screenshots/JamfHelper-Light.png
--------------------------------------------------------------------------------
/Screenshots/SwiftDialog-Light.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sgmills/PrivilegesDemoter/HEAD/Screenshots/SwiftDialog-Light.png
--------------------------------------------------------------------------------
/Screenshots/IBMNotifier-HelpBtn-Dark.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sgmills/PrivilegesDemoter/HEAD/Screenshots/IBMNotifier-HelpBtn-Dark.png
--------------------------------------------------------------------------------
/Screenshots/IBMNotifier-HelpBtn-Light.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sgmills/PrivilegesDemoter/HEAD/Screenshots/IBMNotifier-HelpBtn-Light.png
--------------------------------------------------------------------------------
/Screenshots/SwiftDialog-HelpBtn-Dark.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sgmills/PrivilegesDemoter/HEAD/Screenshots/SwiftDialog-HelpBtn-Dark.png
--------------------------------------------------------------------------------
/Screenshots/SwiftDialog-HelpInfo-Dark.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sgmills/PrivilegesDemoter/HEAD/Screenshots/SwiftDialog-HelpInfo-Dark.png
--------------------------------------------------------------------------------
/Screenshots/SwiftDialog-HelpLink-Light.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/sgmills/PrivilegesDemoter/HEAD/Screenshots/SwiftDialog-HelpLink-Light.png
--------------------------------------------------------------------------------
/pkg/postinstall.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | # Load launchDaemon to check for admin every 5 minutes
4 | launchctl bootstrap system /Library/LaunchDaemons/blog.mostlymac.privileges.check.plist
--------------------------------------------------------------------------------
/pkg/blog.mostlymac.PrivilegesDemoter.conf:
--------------------------------------------------------------------------------
1 | # logfilename [owner:group] mode count size(KiB) when flags [/pid_file] # [sig_num]
2 | /var/log/privileges.log root:wheel 644 10 25000 * Z
--------------------------------------------------------------------------------
/pkg/blog.mostlymac.privileges.check.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 |
6 | Label
7 | blog.mostlymac.privileges.check
8 | Program
9 | /usr/local/mostlymac/PrivilegesDemoter.sh
10 | StartInterval
11 | 300
12 |
13 |
14 |
--------------------------------------------------------------------------------
/pkg/blog.mostlymac.demoteonlogin.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | AssociatedBundleIdentifiers
6 |
7 | corp.sap.privileges
8 |
9 | Label
10 | blog.mostlymac.demoteonlogin
11 | ProgramArguments
12 |
13 | /Applications/Privileges.app/Contents/Resources/PrivilegesCLI
14 | --remove
15 |
16 | RunAtLoad
17 |
18 |
19 |
20 |
--------------------------------------------------------------------------------
/Screenshots/README.md:
--------------------------------------------------------------------------------
1 | # Screenshots
2 |
3 | IBM Notifier with a help button - Light Mode
4 |
5 | 
6 |
7 | IBM Notifier with a help button - Dark Mode
8 |
9 | 
10 |
11 | Swift Dialog - Light Mode
12 |
13 | 
14 |
15 | Swift Dialog with a help button - Dark Mode
16 |
17 | 
18 |
19 | Swift Dialog showing help text - Dark Mode
20 |
21 | 
22 |
23 | Swift Dialog with a "More Info" URL link
24 |
25 | 
26 |
27 | Jamf Helper - Light Mode
28 |
29 | 
30 |
31 | Jamf Helper - Dark Mode
32 |
33 | 
--------------------------------------------------------------------------------
/pkg/preinstall_Standalone.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | # Unload launchDaemon to check for admin every 5 minutes
4 | launchctl bootout system /Library/LaunchDaemons/blog.mostlymac.privileges.check.plist 2>/dev/null
5 |
6 | # Remove old script/folder
7 | rm -rf /usr/local/mostlymac 2>/dev/null
8 |
9 | # Remove check file
10 | rm /tmp/privilegesCheck 2>/dev/null
11 |
12 | # Note: All of the following are depricated as of v3. Removing unnecessary bits
13 | # Remove launchDaemon to confirm and optionally demote user if time limit is reached
14 | launchctl bootout system /Library/LaunchDaemons/blog.mostlymac.privileges.demote.plist 2>/dev/null
15 | launchctl remove /Library/LaunchDaemons/blog.mostlymac.privileges.demote.plist 2>/dev/null
16 | rm -f Library/LaunchDaemons/blog.mostlymac.privileges.demote.plist 2>/dev/null
17 |
18 | # Remove demote file
19 | rm /tmp/privilegesDemote 2>/dev/null
20 |
21 | # Remove cached demotion script
22 | rm -rf "/Library/Application Support/JAMF/Offline Policies/privilegesDemote" 2>/dev/null
--------------------------------------------------------------------------------
/pkg/preinstall_SAPPrivileges.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | # Determine working directory
4 | install_dir=$( dirname "$0" )
5 |
6 | # Install Privileges.app in the working directory
7 | /usr/sbin/installer -dumplog -verbose -pkg "$install_dir"/"Privileges-1.5.3.pkg" -target "$3"
8 |
9 | # Unload launchDaemon to check for admin every 5 minutes
10 | launchctl bootout system /Library/LaunchDaemons/blog.mostlymac.privileges.check.plist 2>/dev/null
11 |
12 | # Remove old script/folder
13 | rm -rf /usr/local/mostlymac 2>/dev/null
14 |
15 | # Remove check file
16 | rm /tmp/privilegesCheck 2>/dev/null
17 |
18 | # Note: All of the following are depricated as of v3. Removing unnecessary bits
19 | # Remove launchDaemon to confirm and optionally demote user if time limit is reached
20 | launchctl bootout system /Library/LaunchDaemons/blog.mostlymac.privileges.demote.plist 2>/dev/null
21 | launchctl remove /Library/LaunchDaemons/blog.mostlymac.privileges.demote.plist 2>/dev/null
22 | rm -f Library/LaunchDaemons/blog.mostlymac.privileges.demote.plist 2>/dev/null
23 |
24 | # Remove demote file
25 | rm /tmp/privilegesDemote 2>/dev/null
26 |
27 | # Remove cached demotion script
28 | rm -rf "/Library/Application Support/JAMF/Offline Policies/privilegesDemote" 2>/dev/null
--------------------------------------------------------------------------------
/pkg/preinstall_Standalone_SwiftDialog.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | # Determine working directory
4 | install_dir=$( dirname "$0" )
5 |
6 | # Install Swift Dialog in the working directory
7 | /usr/sbin/installer -dumplog -verbose -pkg "$install_dir"/"dialog-2.2.0-4535.pkg" -target "$3"
8 |
9 | # Unload launchDaemon to check for admin every 5 minutes
10 | launchctl bootout system /Library/LaunchDaemons/blog.mostlymac.privileges.check.plist 2>/dev/null
11 |
12 | # Remove old script/folder
13 | rm -rf /usr/local/mostlymac 2>/dev/null
14 |
15 | # Remove check file
16 | rm /tmp/privilegesCheck 2>/dev/null
17 |
18 | # Note: All of the following are depricated as of v3. Removing unnecessary bits
19 | # Remove launchDaemon to confirm and optionally demote user if time limit is reached
20 | launchctl bootout system /Library/LaunchDaemons/blog.mostlymac.privileges.demote.plist 2>/dev/null
21 | launchctl remove /Library/LaunchDaemons/blog.mostlymac.privileges.demote.plist 2>/dev/null
22 | rm -f Library/LaunchDaemons/blog.mostlymac.privileges.demote.plist 2>/dev/null
23 |
24 | # Remove demote file
25 | rm /tmp/privilegesDemote 2>/dev/null
26 |
27 | # Remove cached demotion script
28 | rm -rf "/Library/Application Support/JAMF/Offline Policies/privilegesDemote" 2>/dev/null
--------------------------------------------------------------------------------
/pkg/preinstall_SAPPrivileges_SwiftDialog.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | # Determine working directory
4 | install_dir=$( dirname "$0" )
5 |
6 | # Install Privileges.app in the working directory
7 | /usr/sbin/installer -dumplog -verbose -pkg "$install_dir"/"Privileges-1.5.3.pkg" -target "$3"
8 |
9 | # Install Swift Dialog in the working directory
10 | /usr/sbin/installer -dumplog -verbose -pkg "$install_dir"/"dialog-2.2.0-4535.pkg" -target "$3"
11 |
12 | # Unload launchDaemon to check for admin every 5 minutes
13 | launchctl bootout system /Library/LaunchDaemons/blog.mostlymac.privileges.check.plist 2>/dev/null
14 |
15 | # Remove old script/folder
16 | rm -rf /usr/local/mostlymac 2>/dev/null
17 |
18 | # Remove check file
19 | rm /tmp/privilegesCheck 2>/dev/null
20 |
21 | # Note: All of the following are depricated as of v3. Removing unnecessary bits
22 | # Remove launchDaemon to confirm and optionally demote user if time limit is reached
23 | launchctl bootout system /Library/LaunchDaemons/blog.mostlymac.privileges.demote.plist 2>/dev/null
24 | launchctl remove /Library/LaunchDaemons/blog.mostlymac.privileges.demote.plist 2>/dev/null
25 | rm -f Library/LaunchDaemons/blog.mostlymac.privileges.demote.plist 2>/dev/null
26 |
27 | # Remove demote file
28 | rm /tmp/privilegesDemote 2>/dev/null
29 |
30 | # Remove cached demotion script
31 | rm -rf "/Library/Application Support/JAMF/Offline Policies/privilegesDemote" 2>/dev/null
--------------------------------------------------------------------------------
/Configuration Profiles/Example_BackgroundServiceManagement.mobileconfig:
--------------------------------------------------------------------------------
1 |
2 |
4 |
5 |
6 | PayloadContent
7 |
8 |
9 | PayloadDescription
10 | Background Service Management
11 | PayloadDisplayName
12 | Background Service Management
13 | PayloadIdentifier
14 | blog.mostlymac.8838F933-3976-4F41-B730-C737AEE10FCC
15 | PayloadUUID
16 | 8838F933-3976-4F41-B730-C737AEE10FCC
17 | PayloadType
18 | com.apple.servicemanagement
19 | PayloadOrganization
20 | Any Org
21 | Rules
22 |
23 |
24 | RuleType
25 | LabelPrefix
26 | RuleValue
27 | blog.mostlymac
28 | Comment
29 | PrivilegesDemoter
30 |
31 |
32 |
33 |
34 | PayloadDisplayName
35 | Background Service Management
36 | PayloadIdentifier
37 | com.apple.servicemanagement.9AFE5E7F-281E-4034-96AC-A1C64A2370B1
38 | PayloadUUID
39 | 9AFE5E7F-281E-4034-96AC-A1C64A2370B1
40 | PayloadType
41 | Configuration
42 | PayloadScope
43 | System
44 |
45 |
--------------------------------------------------------------------------------
/Configuration Profiles/Example_DockToggleTimeout.mobileconfig:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | PayloadContent
6 |
7 |
8 | PayloadContent
9 |
10 | corp.sap.privileges
11 |
12 | Forced
13 |
14 |
15 | mcx_preference_settings
16 |
17 | DockToggleTimeout
18 | 15
19 |
20 |
21 |
22 |
23 |
24 | PayloadDescription
25 |
26 | PayloadDisplayName
27 | Privileges configuration
28 | PayloadEnabled
29 |
30 | PayloadIdentifier
31 | com.apple.ManagedClient.preferences.AEC544EE-591D-4834-900E-4C94858E7871
32 | PayloadOrganization
33 | SAP SE
34 | PayloadType
35 | com.apple.ManagedClient.preferences
36 | PayloadUUID
37 | AEC544EE-591D-4834-900E-4C94858E7871
38 | PayloadVersion
39 | 1
40 |
41 |
42 | PayloadDescription
43 | Configures the Privileges app.
44 | PayloadDisplayName
45 | Privileges configuration
46 | PayloadEnabled
47 |
48 | PayloadIdentifier
49 | 82A2ED21-27DA-428E-8516-446A52169C71
50 | PayloadOrganization
51 | SAP SE
52 | PayloadRemovalDisallowed
53 |
54 | PayloadScope
55 | System
56 | PayloadType
57 | Configuration
58 | PayloadUUID
59 | C2F39834-001F-4930-AC7D-E5BA0DE82529
60 | PayloadVersion
61 | 1
62 |
63 |
64 |
--------------------------------------------------------------------------------
/Uninstall PrivilegesDemoter.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | ############################################
4 | # Elevate the user to admin before removal #
5 | ############################################
6 |
7 | # Comment out if you want the user left as standard
8 | /usr/local/mostlymac/PrivilegesDemoter.sh --elevate
9 |
10 | #########################################
11 | # This removes all of PrivilegesDemoter #
12 | #########################################
13 |
14 | # Remove launchDaemon to check for admin every 5 minutes
15 | launchctl bootout system "/Library/LaunchDaemons/blog.mostlymac.privileges.check.plist" 2>/dev/null
16 | launchctl remove "/Library/LaunchDaemons/blog.mostlymac.privileges.check.plist" 2>/dev/null
17 | rm -f "/Library/LaunchDaemons/blog.mostlymac.privileges.check.plist" 2>/dev/null
18 |
19 | # Remove launchDaemon to confirm and optionally demote user if time limit is reached
20 | launchctl bootout system "/Library/LaunchDaemons/blog.mostlymac.privileges.demote.plist" 2>/dev/null
21 | launchctl remove "/Library/LaunchDaemons/blog.mostlymac.privileges.demote.plist" 2>/dev/null
22 | rm -f "/Library/LaunchDaemons/blog.mostlymac.privileges.demote.plist" 2>/dev/null
23 |
24 | # Remove script/folder
25 | rm -rf "/usr/local/mostlymac" 2>/dev/null
26 |
27 | # Remove check file
28 | rm "/tmp/privilegesCheck" 2>/dev/null
29 |
30 | # Remove demote file
31 | rm "/tmp/privilegesDemote" 2>/dev/null
32 |
33 | # Remove cached demotion script
34 | rm -rf "/Library/Application Support/JAMF/Offline Policies/privilegesDemote" 2>/dev/null
35 |
36 | # Remove the log rotation config
37 | rm -f "/private/etc/newsyslog.d/blog.mostlymac.PrivilegesDemoter.conf" 2>/dev/null
38 |
39 | # Remove logs
40 | rm -f "/var/log/privileges.log"
41 |
42 | ##########################################################
43 | # Uncomment the following lines to remove SAP Privileges #
44 | ##########################################################
45 |
46 | # Remove SAP Privileges
47 | #rm -rf "/Applications/Privileges.app"
48 |
49 | # Remove SAP Privileges helper
50 | #rm -f "/Library/LaunchDaemons/corp.sap.privileges.helper.plist"
51 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # PrivilegesDemoter
2 |
3 | ## [Please refer to the wiki for detailed documentation](https://github.com/sgmills/PrivilegesDemoter/wiki)
4 |
5 | PrivilegesDemoter allows users to self manage local administrator rights, while reminding them not to operate as an administrator for extended periods of time. Additionally, each elevation and demotion event is recorded and saved to a log file.
6 |
7 | PrivilegesDemoter 3.0 has been written to be customizable for a number of different deployment scenarios. PrivilegesDemoter may be used on its own in standalone mode, or conjunction with [SAP Privileges](https://github.com/SAP/macOS-enterprise-privileges). It may be configured to notify users with [IBM Notifier](https://github.com/IBM/mac-ibm-notifications), [Swift Dialog](https://github.com/bartreardon/swiftDialog), or [Jamf Helper](https://learn.jamf.com/bundle/jamf-pro-documentation-current/page/Applications_and_Utilities.html).
8 |
9 | The PrivilegesDemoter script runs every 5 minutes to check if the currently logged in user is an administrator. If this user is an admin, it adds a timestamp to a file and calculates how long the user has had admin rights. Once that calculation passes a certain threshold, the user is reminded to operate as a standard user whenever possible:
10 |
11 |
12 |
13 | - Clicking “Yes” resets the timer allowing the user to remain an administrator for another period of time, at which point the reminder will reappear.
14 | - Clicking “No” revokes administrator privileges immediately.
15 | - If the user does nothing, the reminder will timeout and revoke administrator privileges in the background.
16 | - Users may use the Privileges application or a self-service policy to gain administrator rights again whenever needed.
17 | - Each privilege escalation and demotion event is logged in `/var/log/privileges.log`
18 |
19 | ## Configuration
20 |
21 | As of version 3.0 and higher, PrivilegesDemoter is configured using a Configuration Profile. This script was originally designed to work with Macs enrolled in Jamf Pro with SAP Privileges installed. Versions 3.0 and higher have additional options for use with other agents and workflows. Please see the [wiki](https://github.com/sgmills/PrivilegesDemoter/wiki) for more information on available options.
--------------------------------------------------------------------------------
/Configuration Profiles/Example_PrivilegesDemoter3.mobileconfig:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | PayloadContent
6 |
7 |
8 | PayloadContent
9 |
10 | blog.mostlymac.privilegesdemoter
11 |
12 | Forced
13 |
14 |
15 | mcx_preference_settings
16 |
17 | notificationAgent
18 |
19 | ibmNotifier
20 |
21 | swiftDialog
22 |
23 | disableNotifications
24 |
25 |
26 | ibmNotifierSettings
27 |
28 | notificationSound
29 |
30 | ibmNotifierPath
31 | /Applications/IBM Notifier.app
32 | ibmNotifierBinary
33 | IBM Notifier
34 |
35 | helpButton
36 |
37 | helpButtonStatus
38 |
39 | helpButtonType
40 | infopopup
41 | helpButtonPayload
42 | Questions? Contact the Help Desk at 555-1234
43 |
44 | mainText
45 | You are currently an administrator on this device.\n\nIt is recommended to operate as a standard user whenever possible.\n\nDo you still need elevated privileges?
46 | excludedAdmins
47 | orgAdmin, jamfAdmin
48 | reminderThreshold
49 | 15
50 | jamfProSettings
51 |
52 | useJamfPolicy
53 |
54 | jamfTrigger
55 | demote-user
56 |
57 | standaloneMode
58 |
59 |
60 |
61 |
62 |
63 |
64 | PayloadDisplayName
65 | PrivilegesDemoter Settings
66 | PayloadIdentifier
67 | 0B9C25FB-281A-482A-AB80-4551BB168B7E
68 | PayloadOrganization
69 | Any Org
70 | PayloadType
71 | com.apple.ManagedClient.preferences
72 | PayloadUUID
73 | 0B9C25FB-281A-482A-AB80-4551BB168B7E
74 | PayloadVersion
75 | 1
76 |
77 |
78 | PayloadDescription
79 | Settings for PrivilegesDemoter
80 | PayloadDisplayName
81 | PrivilegesDemoter Settings
82 | PayloadEnabled
83 |
84 | PayloadIdentifier
85 | 6AD24F25-8E72-41A3-9C4A-3C9F991BDB0D
86 | PayloadOrganization
87 | Any Org
88 | PayloadRemovalDisallowed
89 |
90 | PayloadScope
91 | System
92 | PayloadType
93 | Configuration
94 | PayloadUUID
95 | 6AD24F25-8E72-41A3-9C4A-3C9F991BDB0D
96 | PayloadVersion
97 | 1
98 |
99 |
100 |
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | Apache License
2 | Version 2.0, January 2004
3 | http://www.apache.org/licenses/
4 |
5 | TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
6 |
7 | 1. Definitions.
8 |
9 | "License" shall mean the terms and conditions for use, reproduction,
10 | and distribution as defined by Sections 1 through 9 of this document.
11 |
12 | "Licensor" shall mean the copyright owner or entity authorized by
13 | the copyright owner that is granting the License.
14 |
15 | "Legal Entity" shall mean the union of the acting entity and all
16 | other entities that control, are controlled by, or are under common
17 | control with that entity. For the purposes of this definition,
18 | "control" means (i) the power, direct or indirect, to cause the
19 | direction or management of such entity, whether by contract or
20 | otherwise, or (ii) ownership of fifty percent (50%) or more of the
21 | outstanding shares, or (iii) beneficial ownership of such entity.
22 |
23 | "You" (or "Your") shall mean an individual or Legal Entity
24 | exercising permissions granted by this License.
25 |
26 | "Source" form shall mean the preferred form for making modifications,
27 | including but not limited to software source code, documentation
28 | source, and configuration files.
29 |
30 | "Object" form shall mean any form resulting from mechanical
31 | transformation or translation of a Source form, including but
32 | not limited to compiled object code, generated documentation,
33 | and conversions to other media types.
34 |
35 | "Work" shall mean the work of authorship, whether in Source or
36 | Object form, made available under the License, as indicated by a
37 | copyright notice that is included in or attached to the work
38 | (an example is provided in the Appendix below).
39 |
40 | "Derivative Works" shall mean any work, whether in Source or Object
41 | form, that is based on (or derived from) the Work and for which the
42 | editorial revisions, annotations, elaborations, or other modifications
43 | represent, as a whole, an original work of authorship. For the purposes
44 | of this License, Derivative Works shall not include works that remain
45 | separable from, or merely link (or bind by name) to the interfaces of,
46 | the Work and Derivative Works thereof.
47 |
48 | "Contribution" shall mean any work of authorship, including
49 | the original version of the Work and any modifications or additions
50 | to that Work or Derivative Works thereof, that is intentionally
51 | submitted to Licensor for inclusion in the Work by the copyright owner
52 | or by an individual or Legal Entity authorized to submit on behalf of
53 | the copyright owner. For the purposes of this definition, "submitted"
54 | means any form of electronic, verbal, or written communication sent
55 | to the Licensor or its representatives, including but not limited to
56 | communication on electronic mailing lists, source code control systems,
57 | and issue tracking systems that are managed by, or on behalf of, the
58 | Licensor for the purpose of discussing and improving the Work, but
59 | excluding communication that is conspicuously marked or otherwise
60 | designated in writing by the copyright owner as "Not a Contribution."
61 |
62 | "Contributor" shall mean Licensor and any individual or Legal Entity
63 | on behalf of whom a Contribution has been received by Licensor and
64 | subsequently incorporated within the Work.
65 |
66 | 2. Grant of Copyright License. Subject to the terms and conditions of
67 | this License, each Contributor hereby grants to You a perpetual,
68 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
69 | copyright license to reproduce, prepare Derivative Works of,
70 | publicly display, publicly perform, sublicense, and distribute the
71 | Work and such Derivative Works in Source or Object form.
72 |
73 | 3. Grant of Patent License. Subject to the terms and conditions of
74 | this License, each Contributor hereby grants to You a perpetual,
75 | worldwide, non-exclusive, no-charge, royalty-free, irrevocable
76 | (except as stated in this section) patent license to make, have made,
77 | use, offer to sell, sell, import, and otherwise transfer the Work,
78 | where such license applies only to those patent claims licensable
79 | by such Contributor that are necessarily infringed by their
80 | Contribution(s) alone or by combination of their Contribution(s)
81 | with the Work to which such Contribution(s) was submitted. If You
82 | institute patent litigation against any entity (including a
83 | cross-claim or counterclaim in a lawsuit) alleging that the Work
84 | or a Contribution incorporated within the Work constitutes direct
85 | or contributory patent infringement, then any patent licenses
86 | granted to You under this License for that Work shall terminate
87 | as of the date such litigation is filed.
88 |
89 | 4. Redistribution. You may reproduce and distribute copies of the
90 | Work or Derivative Works thereof in any medium, with or without
91 | modifications, and in Source or Object form, provided that You
92 | meet the following conditions:
93 |
94 | (a) You must give any other recipients of the Work or
95 | Derivative Works a copy of this License; and
96 |
97 | (b) You must cause any modified files to carry prominent notices
98 | stating that You changed the files; and
99 |
100 | (c) You must retain, in the Source form of any Derivative Works
101 | that You distribute, all copyright, patent, trademark, and
102 | attribution notices from the Source form of the Work,
103 | excluding those notices that do not pertain to any part of
104 | the Derivative Works; and
105 |
106 | (d) If the Work includes a "NOTICE" text file as part of its
107 | distribution, then any Derivative Works that You distribute must
108 | include a readable copy of the attribution notices contained
109 | within such NOTICE file, excluding those notices that do not
110 | pertain to any part of the Derivative Works, in at least one
111 | of the following places: within a NOTICE text file distributed
112 | as part of the Derivative Works; within the Source form or
113 | documentation, if provided along with the Derivative Works; or,
114 | within a display generated by the Derivative Works, if and
115 | wherever such third-party notices normally appear. The contents
116 | of the NOTICE file are for informational purposes only and
117 | do not modify the License. You may add Your own attribution
118 | notices within Derivative Works that You distribute, alongside
119 | or as an addendum to the NOTICE text from the Work, provided
120 | that such additional attribution notices cannot be construed
121 | as modifying the License.
122 |
123 | You may add Your own copyright statement to Your modifications and
124 | may provide additional or different license terms and conditions
125 | for use, reproduction, or distribution of Your modifications, or
126 | for any such Derivative Works as a whole, provided Your use,
127 | reproduction, and distribution of the Work otherwise complies with
128 | the conditions stated in this License.
129 |
130 | 5. Submission of Contributions. Unless You explicitly state otherwise,
131 | any Contribution intentionally submitted for inclusion in the Work
132 | by You to the Licensor shall be under the terms and conditions of
133 | this License, without any additional terms or conditions.
134 | Notwithstanding the above, nothing herein shall supersede or modify
135 | the terms of any separate license agreement you may have executed
136 | with Licensor regarding such Contributions.
137 |
138 | 6. Trademarks. This License does not grant permission to use the trade
139 | names, trademarks, service marks, or product names of the Licensor,
140 | except as required for reasonable and customary use in describing the
141 | origin of the Work and reproducing the content of the NOTICE file.
142 |
143 | 7. Disclaimer of Warranty. Unless required by applicable law or
144 | agreed to in writing, Licensor provides the Work (and each
145 | Contributor provides its Contributions) on an "AS IS" BASIS,
146 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
147 | implied, including, without limitation, any warranties or conditions
148 | of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
149 | PARTICULAR PURPOSE. You are solely responsible for determining the
150 | appropriateness of using or redistributing the Work and assume any
151 | risks associated with Your exercise of permissions under this License.
152 |
153 | 8. Limitation of Liability. In no event and under no legal theory,
154 | whether in tort (including negligence), contract, or otherwise,
155 | unless required by applicable law (such as deliberate and grossly
156 | negligent acts) or agreed to in writing, shall any Contributor be
157 | liable to You for damages, including any direct, indirect, special,
158 | incidental, or consequential damages of any character arising as a
159 | result of this License or out of the use or inability to use the
160 | Work (including but not limited to damages for loss of goodwill,
161 | work stoppage, computer failure or malfunction, or any and all
162 | other commercial damages or losses), even if such Contributor
163 | has been advised of the possibility of such damages.
164 |
165 | 9. Accepting Warranty or Additional Liability. While redistributing
166 | the Work or Derivative Works thereof, You may choose to offer,
167 | and charge a fee for, acceptance of support, warranty, indemnity,
168 | or other liability obligations and/or rights consistent with this
169 | License. However, in accepting such obligations, You may act only
170 | on Your own behalf and on Your sole responsibility, not on behalf
171 | of any other Contributor, and only if You agree to indemnify,
172 | defend, and hold each Contributor harmless for any liability
173 | incurred by, or claims asserted against, such Contributor by reason
174 | of your accepting any such warranty or additional liability.
175 |
176 | END OF TERMS AND CONDITIONS
177 |
178 | APPENDIX: How to apply the Apache License to your work.
179 |
180 | To apply the Apache License to your work, attach the following
181 | boilerplate notice, with the fields enclosed by brackets "[]"
182 | replaced with your own identifying information. (Don't include
183 | the brackets!) The text should be enclosed in the appropriate
184 | comment syntax for the file format. We also recommend that a
185 | file or class name and description of purpose be included on the
186 | same "printed page" as the copyright notice for easier
187 | identification within third-party archives.
188 |
189 | Copyright [yyyy] [name of copyright owner]
190 |
191 | Licensed under the Apache License, Version 2.0 (the "License");
192 | you may not use this file except in compliance with the License.
193 | You may obtain a copy of the License at
194 |
195 | http://www.apache.org/licenses/LICENSE-2.0
196 |
197 | Unless required by applicable law or agreed to in writing, software
198 | distributed under the License is distributed on an "AS IS" BASIS,
199 | WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
200 | See the License for the specific language governing permissions and
201 | limitations under the License.
202 |
--------------------------------------------------------------------------------
/JSON Schema - PrivilegesDemoter-3.0.json:
--------------------------------------------------------------------------------
1 | {
2 | "title": "PrivilegesDemoter (blog.mostlymac.privilegesdemoter)",
3 | "description": "Preference settings for PrivilegesDemoter",
4 | "links": [
5 | {
6 | "rel": "More information",
7 | "href": "https://github.com/sgmills/PrivilegesDemoter"
8 | }
9 | ],
10 | "properties": {
11 | "notificationAgent": {
12 | "description": "Select how the user will be notified. Configure ONLY ONE agent. If not configured, Jamf Helper is default.",
13 | "title": "Notification Agent",
14 | "anyOf": [
15 | {
16 | "title": "Not Configured",
17 | "type": "null"
18 | },
19 | {
20 | "title": "Configured",
21 | "type": "object",
22 | "properties": {
23 | "ibmNotifier": {
24 | "description": "Use IBM Notifier as the notification agent.",
25 | "title": "IBM Notifier",
26 | "anyOf": [
27 | {
28 | "title": "Not Configured",
29 | "type": "null"
30 | },
31 | {
32 | "title": "Configured",
33 | "type": "boolean"
34 | }
35 | ]
36 | },
37 | "swiftDialog": {
38 | "description": "Use SwiftDialog as the notification agent.",
39 | "title": "Swift Dialog",
40 | "anyOf": [
41 | {
42 | "title": "Not Configured",
43 | "type": "null"
44 | },
45 | {
46 | "title": "Configured",
47 | "type": "boolean"
48 | }
49 | ]
50 | },
51 | "jamfHelper": {
52 | "description": "(Optional) Use Jamf Helper as the notification agent. Default is Jamf Helper even if this key is not set.",
53 | "title": "Jamf Helper",
54 | "anyOf": [
55 | {
56 | "title": "Not Configured",
57 | "type": "null"
58 | },
59 | {
60 | "title": "Configured",
61 | "type": "boolean"
62 | }
63 | ]
64 | },
65 | "disableNotifications": {
66 | "description": "Do not show notifications. Silently demote the user in the background.",
67 | "title": "Disable Notifications",
68 | "anyOf": [
69 | {
70 | "title": "Not Configured",
71 | "type": "null"
72 | },
73 | {
74 | "title": "Configured",
75 | "type": "boolean"
76 | }
77 | ]
78 | }
79 | }
80 | }
81 | ]
82 | },
83 | "ibmNotifierSettings": {
84 | "description": "Custom settings only available when IBM Notifier is set to true.",
85 | "title": "Optional IBM Notifier Settings",
86 | "anyOf": [
87 | {
88 | "title": "Not Configured",
89 | "type": "null"
90 | },
91 | {
92 | "title": "Configured",
93 | "type": "object",
94 | "properties": {
95 | "notificationSound": {
96 | "description": "Play a sound when notification appears. Default: true",
97 | "title": "IBM Notifier Sound",
98 | "anyOf": [
99 | {
100 | "title": "Not Configured",
101 | "type": "null"
102 | },
103 | {
104 | "title": "Configured",
105 | "type": "boolean"
106 | }
107 | ]
108 | },
109 | "ibmNotifierPath": {
110 | "description": "Path to IBM Notifier application. Only required if IBM Notifier is not at the default location.",
111 | "title": "IBM Notifier Custom Path",
112 | "anyOf": [
113 | {
114 | "title": "Not Configured",
115 | "type": "null"
116 | },
117 | {
118 | "title": "Configured",
119 | "type": "string",
120 | "options": {
121 | "inputAttributes": {
122 | "placeholder": "/Applications/IBM Notifier.app"
123 | }
124 | }
125 | }
126 | ]
127 | },
128 | "ibmNotifierBinary": {
129 | "description": "IBM Notifier binary name. Only required if app is rebranded with different binary name.",
130 | "title": "Rebranded IBM Notifier",
131 | "anyOf": [
132 | {
133 | "title": "Not Configured",
134 | "type": "null"
135 | },
136 | {
137 | "title": "Configured",
138 | "links": [
139 | {
140 | "rel": "More information",
141 | "href": "https://github.com/IBM/mac-ibm-notifications/wiki/Rebranding-the-application"
142 | }
143 | ],
144 | "type": "string",
145 | "options": {
146 | "inputAttributes": {
147 | "placeholder": "IBM Notifier"
148 | }
149 | }
150 | }
151 | ]
152 | }
153 | }
154 | }
155 | ]
156 | },
157 | "helpButton": {
158 | "description": "Help button is ONLY available when using IBM Notifier or Swift Dialog as the notification agent.",
159 | "title": "Help Button",
160 | "anyOf": [
161 | {
162 | "title": "Not Configured",
163 | "type": "null"
164 | },
165 | {
166 | "title": "Configured",
167 | "type": "object",
168 | "properties": {
169 | "helpButtonStatus": {
170 | "title": "Help Button Status",
171 | "description": "Enable the help button.",
172 | "type": "boolean"
173 | },
174 | "helpButtonType": {
175 | "title": "Help Button Type",
176 | "description": "Use infopopup type for text or link type for a URL.",
177 | "type": "string",
178 | "enum": [
179 | "infopopup",
180 | "link"
181 | ]
182 | },
183 | "helpButtonPayload": {
184 | "title": "Help Button Payload",
185 | "description": "Either text or a URL depending on the type set above.",
186 | "type": "string"
187 | }
188 | }
189 | }
190 | ]
191 | },
192 | "mainText": {
193 | "title": "Main Text",
194 | "anyOf": [
195 | {
196 | "title": "Not Configured",
197 | "type": "null"
198 | },
199 | {
200 | "title": "Configured",
201 | "description": "Reminder text that is displayed to the user. Use \\n for a new line.",
202 | "type": "string",
203 | "options": {
204 | "inputAttributes": {
205 | "placeholder": "You are currently an administrator on this device.\n\nIt is recommended to operate as a standard user whenever possible.\n\nDo you still require elevated privileges?"
206 | }
207 | }
208 | }
209 | ]
210 | },
211 | "excludedAdmins": {
212 | "description": "Comma-separated list of administrators to exclude from demotion.",
213 | "title": "Excluded Admins",
214 | "anyOf": [
215 | {
216 | "title": "Not Configured",
217 | "type": "null"
218 | },
219 | {
220 | "title": "Configured",
221 | "type": "string",
222 | "options": {
223 | "inputAttributes": {
224 | "placeholder": "OrgAdmin, JamfAdmin"
225 | }
226 | }
227 | }
228 | ]
229 | },
230 | "reminderThreshold": {
231 | "description": "Amount of time (in minutes) before administrators see the reminder and/or get demoted to standard.",
232 | "title": "Reminder Threshold",
233 | "anyOf": [
234 | {
235 | "title": "Not Configured",
236 | "type": "null"
237 | },
238 | {
239 | "title": "Configured",
240 | "type": "integer"
241 | }
242 | ]
243 | },
244 | "jamfProSettings": {
245 | "description": "Allows running the demotion script from Jamf Pro instead of locally.",
246 | "title": "Jamf Pro Settings",
247 | "anyOf": [
248 | {
249 | "title": "Not Configured",
250 | "type": "null"
251 | },
252 | {
253 | "title": "Configured",
254 | "type": "object",
255 | "properties": {
256 | "useJamfPolicy": {
257 | "description": "Use a jamf policy to demote the user.",
258 | "title": "Use Jamf Policy",
259 | "anyOf": [
260 | {
261 | "title": "Not Configured",
262 | "type": "null"
263 | },
264 | {
265 | "title": "Configured",
266 | "type": "boolean"
267 | }
268 | ]
269 | },
270 | "jamfTrigger": {
271 | "description": "Customize the jamf trigger used for demotion. Default is privilegesDemote",
272 | "title": "Jamf Policy Trigger",
273 | "anyOf": [
274 | {
275 | "title": "Not Configured",
276 | "type": "null"
277 | },
278 | {
279 | "title": "Configured",
280 | "type": "string",
281 | "options": {
282 | "inputAttributes": {
283 | "placeholder": "privilegesDemote"
284 | }
285 | }
286 | }
287 | ]
288 | }
289 | }
290 | }
291 | ]
292 | },
293 | "standaloneMode": {
294 | "description": "Use PrivilegesDemoter alone, without the SAP Privileges app.",
295 | "title": "Standalone Mode",
296 | "anyOf": [
297 | {
298 | "title": "Not Configured",
299 | "type": "null"
300 | },
301 | {
302 | "title": "Configured",
303 | "type": "boolean"
304 | }
305 | ]
306 | }
307 | }
308 | }
--------------------------------------------------------------------------------
/Demote Admin Privileges.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | ####################################################################################################
4 | #
5 | # SCRIPT: Demote Admin Privileges
6 | # AUTHOR: Sam Mills (github.com/sgmills)
7 | # DATE: 25 April 2023
8 | # REV: 3.0
9 | #
10 | ####################################################################################################
11 | #
12 | # Description
13 | # This tool reminds users to operate as a standard user. If a given user has had admin privileges
14 | # for longer than a specific threshold, they will be reminded to use standard user rights. Users
15 | # are offered the option to remain admin or demote themselves.
16 | #
17 | # Events to elevate privileges or demote are logged at /var/log/privileges.log.
18 | #
19 | ####################################################################################################
20 | # LOG SETUP #
21 |
22 | # All privilege events logging location
23 | privilegesLog="/var/log/privileges.log"
24 |
25 | # Check if log exists and create if needed
26 | if [ ! -f "$privilegesLog" ]; then
27 | touch "$privilegesLog"
28 | fi
29 |
30 | # Create stamp for logging
31 | stamp="$(date +"%Y-%m-%d %H:%M:%S%z") blog.mostlymac.privileges.demoter"
32 |
33 | # Redirect output to log file and stdout for logging
34 | exec 1> >( tee -a "${privilegesLog}" ) 2>&1
35 |
36 | ####################################################################################################
37 | # SET SCRIPT VARIABLES #
38 |
39 | # PrivilegesDemoter managed preferences plist
40 | pdPrefs="/Library/Managed Preferences/blog.mostlymac.privilegesdemoter.plist"
41 |
42 | if [[ -e "$pdPrefs" ]]; then
43 | # Get help button status
44 | help_button_status="$( /usr/libexec/PlistBuddy -c "print helpButton:helpButtonStatus" "$pdPrefs" 2>/dev/null )"
45 |
46 | # Get the help button type
47 | help_button_type="$( /usr/libexec/PlistBuddy -c "print helpButton:helpButtonType" "$pdPrefs" 2>/dev/null )"
48 |
49 | # Get the help button payload
50 | help_button_payload="$( /usr/libexec/PlistBuddy -c "print helpButton:helpButtonPayload" "$pdPrefs" 2>/dev/null )"
51 |
52 | # Get notification sound setting
53 | notification_sound="$( /usr/libexec/PlistBuddy -c "print ibmNotifierSettings:notificationSound" "$pdPrefs" 2>/dev/null )"
54 |
55 | # Are we using IBM Notifer?
56 | ibm_notifier="$( /usr/libexec/PlistBuddy -c "print notificationAgent:ibmNotifier" "$pdPrefs" 2>/dev/null )"
57 |
58 | # Are we using Swift Dialog?
59 | swift_dialog="$( /usr/libexec/PlistBuddy -c "print notificationAgent:swiftDialog" "$pdPrefs" 2>/dev/null )"
60 |
61 | # Get list of excluded admins
62 | admin_to_exclude="$( /usr/libexec/PlistBuddy -c "print excludedAdmins" "$pdPrefs" 2>/dev/null )"
63 |
64 | # Get silent operation setting
65 | silent="$( /usr/libexec/PlistBuddy -c "print notificationAgent:disableNotifications" "$pdPrefs" 2>/dev/null )"
66 |
67 | # Get setting for standalone mode without SAP Privileges
68 | standalone="$( /usr/libexec/PlistBuddy -c "print standaloneMode" "$pdPrefs" 2>/dev/null )"
69 |
70 | # Get main text for notifications. Set to default if not found
71 | if [[ ! $( /usr/libexec/PlistBuddy -c "print mainText" "$pdPrefs" 2>/dev/null ) ]]; then
72 | main_text=$( printf "You are currently an administrator on this device.\n\nIt is recommended to operate as a standard user whenever possible.\n\nDo you still require elevated privileges?" )
73 | else
74 | get_text="$( /usr/libexec/PlistBuddy -c "print mainText" "$pdPrefs" 2>/dev/null )"
75 | # Strip out extra slash in new line characters
76 | main_text=$( printf "${get_text//'\\n'/\n}" )
77 | fi
78 |
79 | # Check for IBM Notifier path. Set to default if not found
80 | if [[ ! $( /usr/libexec/PlistBuddy -c "print ibmNotifierSettings:ibmNotifierPath" "$pdPrefs" 2>/dev/null ) ]]; then
81 | ibm_notifier_path="/Applications/IBM Notifier.app"
82 | else
83 | ibm_notifier_path="$( /usr/libexec/PlistBuddy -c "print ibmNotifierSettings:ibmNotifierPath" "$pdPrefs" 2>/dev/null )"
84 | fi
85 |
86 | # Check for IBM Notifier custom binary name. Set to default if not found
87 | if [[ ! $( /usr/libexec/PlistBuddy -c "print ibmNotifierSettings:ibmNotifierBinary" "$pdPrefs" 2>/dev/null ) ]]; then
88 | ibm_notifier_binary="IBM Notifier"
89 | else
90 | ibm_notifier_binary="$( /usr/libexec/PlistBuddy -c "print ibmNotifierSettings:ibmNotifierBinary" "$pdPrefs" 2>/dev/null )"
91 | fi
92 | else
93 | main_text=$( printf "You are currently an administrator on this device.\n\nIt is recommended to operate as a standard user whenever possible.\n\nDo you still require elevated privileges?" )
94 | fi
95 |
96 | # Set path to the icon
97 | if [ -f /usr/local/mostlymac/icon.png ]; then
98 | icon="/usr/local/mostlymac/icon.png"
99 | elif [ -f /Applications/Privileges.app/Contents/Resources/AppIcon.icns ]; then
100 | icon="/Applications/Privileges.app/Contents/Resources/AppIcon.icns"
101 | else
102 | icon="/System/Library/CoreServices/CoreTypes.bundle/Contents/Resources/AlertNoteIcon.icns"
103 | fi
104 |
105 | # Set the default path to swift dialog
106 | swift_dialog_path="/usr/local/bin/dialog"
107 |
108 | # Log file which contains the timestamps of the last runs
109 | checkFile="/tmp/privilegesCheck"
110 |
111 | # Location of PrivilegesCLI
112 | privilegesCLI="/Applications/Privileges.app/Contents/Resources/PrivilegesCLI"
113 |
114 | ####################################################################################################
115 | # SET USER AND DEVICE INFO #
116 |
117 | # Get the current user
118 | currentUser=$( scutil <<< "show State:/Users/ConsoleUser" | awk '/Name :/ && ! /loginwindow/ { print $3 }' )
119 |
120 | # Get machine UDID
121 | UDID=$( ioreg -d2 -c IOPlatformExpertDevice | awk -F\" '/IOPlatformUUID/{print $(NF-1)}' )
122 |
123 | ####################################################################################################
124 | # FUNCTIONS #
125 |
126 | # Function to demote the current user
127 | demote () {
128 | if [[ "$standalone" = true ]] || [[ ! -e "${privilegesCLI}" ]]; then
129 | /usr/sbin/dseditgroup -o edit -d "$currentUser" -t user admin
130 | else
131 | launchctl asuser "$currentUserID" sudo -u "$currentUser" "$privilegesCLI" --remove &> /dev/null
132 | fi
133 | }
134 |
135 | # Function to initiate timestamp for admin calculations
136 | initTimestamp () {
137 | # Get current timestamp
138 | timeStamp=$(date +%s)
139 |
140 | # Check if log file exists and create if needed
141 | if [[ ! -f ${checkFile} ]]; then
142 | # Create file with current timestamp
143 | touch ${checkFile}
144 | echo "${timeStamp}" > ${checkFile}
145 | fi
146 | }
147 |
148 | # Function to confirm privileges have been changed successfully and log error after 1 retry.
149 | # Takes user as argument $1
150 | confirmPrivileges () {
151 |
152 | # If user is still admin, try revoking again using dseditgroup
153 | if /usr/sbin/dseditgroup -o checkmember -m "${1}" admin &> /dev/null; then
154 | echo "$stamp Warn: ${1} is still an admin on MachineID: $UDID. Trying again..."
155 | /usr/sbin/dseditgroup -o edit -d "${1}" -t user admin
156 | sleep 1
157 |
158 | # If user was not sucessfully demoted after retry, write error to log. Otherwise log success.
159 | if /usr/sbin/dseditgroup -o checkmember -m "${1}" admin &> /dev/null; then
160 | echo "$stamp Error: Could not demote ${1} to standard on MachineID: $UDID."
161 | else
162 | # Successfully demoted with dseditgroup
163 | # If dock is running and not in standalone mode, reload to display correct tile
164 | if [[ $(/usr/bin/pgrep Dock) -gt 0 ]] && [[ "$standalone" != true ]]; then
165 | /usr/bin/killall Dock
166 | fi
167 |
168 | # Log that user was successfully demoted.
169 | echo "$stamp Status: ${1} is now a standard user on MachineID: $UDID."
170 | fi
171 |
172 | else
173 | # Log that user was successfully demoted.
174 | echo "$stamp Status: ${1} is now a standard user on MachineID: $UDID."
175 | fi
176 |
177 | # Clean up privileges check log file to reset timer
178 | rm "$checkFile" &> /dev/null
179 | }
180 |
181 | # Function to prompt with IBM Notifier
182 | prompt_with_ibmNotifier () {
183 |
184 | # If help button is enabled, set type and payload
185 | if [[ $help_button_status = true ]]; then
186 | help_info=("-help_button_cta_type" "${help_button_type}" "-help_button_cta_payload" "${help_button_payload}")
187 | fi
188 |
189 | # Disable sounds if needed
190 | if [[ $notification_sound = false ]]; then
191 | sound=("-silent")
192 | fi
193 |
194 | # Prompt the user
195 | prompt_user() {
196 | button=$( "${ibm_notifier_path}/Contents/MacOS/${ibm_notifier_binary}" \
197 | -type "popup" \
198 | -bar_title "Privileges Reminder" \
199 | -subtitle "$main_text" \
200 | -icon_path "$icon" \
201 | -main_button_label "No" \
202 | -secondary_button_label "Yes" \
203 | "${help_info[@]}" \
204 | -timeout 120 \
205 | "${sound[@]}" \
206 | -position center \
207 | -always_on_top )
208 |
209 | echo "$?"
210 | }
211 |
212 | # Get the user's response
213 | buttonClicked=$( prompt_user )
214 | }
215 |
216 | # Function to prompt with Swift Dialog
217 | prompt_with_swiftDialog () {
218 |
219 | # If help button is enabled, set message and payload accordingly
220 | if [[ $help_button_status = true ]]; then
221 | if [[ $help_button_type == "infopopup" ]]; then
222 | help_info=("--helpmessage" "$help_button_payload")
223 | elif [[ $help_button_type == "link" ]]; then
224 | help_info=("--infobuttontext" "More Info" "--infobuttonaction" "$help_button_payload")
225 | fi
226 | fi
227 |
228 | # Prompt the user
229 | prompt_user() {
230 | button=$( "${swift_dialog_path}" \
231 | --title none \
232 | --message "$main_text" \
233 | --messagefont size=15 \
234 | --icon "$icon" \
235 | --iconsize 75 \
236 | --button1text No \
237 | --button2text Yes \
238 | "${help_info[@]}" \
239 | --timer 120 \
240 | --height 180 \
241 | --width 520 \
242 | --ontop )
243 |
244 | echo "$?"
245 | }
246 |
247 | # Get the user's response
248 | buttonClicked=$( prompt_user )
249 | }
250 |
251 | # Function to prompt with Jamf Helper
252 | prompt_with_jamfHelper () {
253 |
254 | # Prompt the user
255 | prompt_user() {
256 | button=$( "/Library/Application Support/JAMF/bin/jamfHelper.app/Contents/MacOS/jamfHelper" \
257 | -windowType utility \
258 | -title "Privileges Reminder" \
259 | -description "$main_text" \
260 | -alignDescription left \
261 | -icon "$icon" \
262 | -button1 No \
263 | -button2 Yes \
264 | -defaultButton 1 \
265 | -timeout 120 )
266 |
267 | echo "$?"
268 | }
269 |
270 | # Get the user's response
271 | buttonClicked=$( prompt_user )
272 | }
273 |
274 | # Function to check if array contains a user
275 | containsUser () {
276 | local e
277 | for e in "${@:2}"; do [[ "$e" == "$1" ]] && return 1; done
278 | return 0
279 | }
280 |
281 | # Function to perform admin user demotion
282 | demoteUser () {
283 | # Check for a logged in user
284 | if [[ $currentUser != "" ]]; then
285 |
286 | # Get the current user's UID
287 | currentUserID=$(id -u "$currentUser")
288 |
289 | # If current user is an admin, remove rights
290 | if /usr/sbin/dseditgroup -o checkmember -m "$currentUser" admin &> /dev/null; then
291 |
292 | # Read comma separated list of excluded admins into array
293 | IFS=', ' read -r -a excludedUsers <<< "$admin_to_exclude"
294 |
295 | # Add always excluded users to array
296 | excludedUsers+=("root" "_mbsetupuser")
297 |
298 | # Use function to check if current user is excluded from demotion
299 | containsUser "$currentUser" "${excludedUsers[@]}"
300 | excludedUserLoggedIn="$?"
301 |
302 |
303 | # If current user is excluded from demotion, reset timer and exit
304 | if [[ "$excludedUserLoggedIn" = 1 ]]; then
305 | echo "$stamp Info: Excluded admin user $currentUser logged in on MachineID: $UDID. Will not perform demotion."
306 | # Reset timer and exit 0
307 | rm "$checkFile" &> /dev/null
308 | exit 0
309 | fi
310 |
311 | # User with admin is logged in.
312 | # If silent option is passed, demote silently
313 | if [[ $silent = true ]]; then
314 | # Revoke rights silently
315 | echo "$stamp Info: Silent option used. Removing rights for $currentUser on MachineID: $UDID without notification."
316 | # Use function to demote user
317 | demote
318 |
319 | # Run confirm privileges function with current user.
320 | confirmPrivileges "$currentUser"
321 |
322 | exit
323 |
324 | else
325 | # Notify the user. Use app that user defined and fall back jamf helper
326 | if [[ $ibm_notifier = true ]]; then
327 | if [[ -e "${ibm_notifier_path}" ]]; then
328 | prompt_with_ibmNotifier
329 | else
330 | echo "$stamp Warn: IBM Notifier not found. Defaulting to Jamf Helper for notification."
331 | prompt_with_jamfHelper
332 | fi
333 | elif [[ $swift_dialog = true ]]; then
334 | if [[ -e "${swift_dialog_path}" ]]; then
335 | prompt_with_swiftDialog
336 | else
337 | echo "$stamp Warn: Swift Dialog not found. Defaulting to Jamf Helper for notification."
338 | prompt_with_jamfHelper
339 | fi
340 | else
341 | prompt_with_jamfHelper
342 | fi
343 | fi
344 |
345 | # If the user clicked NO (button 0), remove admin rights immediately
346 | if [[ $buttonClicked = 0 ]]; then
347 | # Revoke rights
348 | echo "$stamp Decision: $currentUser no longer needs admin rights. Removing rights on MachineID: $UDID now."
349 | # Use function to demote user
350 | demote
351 |
352 | # Run confirm privileges function with current user.
353 | confirmPrivileges "$currentUser"
354 |
355 | # If the user clicked YES (button 2) leave admin rights in tact
356 | elif [[ $buttonClicked = 2 ]]; then
357 | echo "$stamp Decision: $currentUser says they still need admin rights on MachineID: $UDID."
358 | echo "$stamp Status: Resetting timer and allowing $currentUser to remain an admin on MachineID: $UDID."
359 |
360 | # Clean up privileges check file to reset timer
361 | rm "$checkFile" &> /dev/null
362 |
363 | # Restart the timer immidiately
364 | initTimestamp
365 |
366 | # If timeout occurred, (exit code 4) remove admin rights
367 | elif [[ $buttonClicked = 4 ]]; then
368 | echo "$stamp Decision: Timeout occurred. Removing admin rights for $currentUser on MachineID: $UDID now."
369 | # Use function to demote user
370 | demote
371 |
372 | # Run confirm privileges function with current user.
373 | confirmPrivileges "$currentUser"
374 |
375 | # If unexpected code is returned, log an error
376 | else
377 | echo "$stamp Error: Unexpected exit code [$buttonClicked] returned from prompt. User: $currentUser, MachineID: $UDID."
378 | fi
379 | else
380 | # Current user is not an admin
381 | echo "$stamp Info: $currentUser is not an admin on MachineID: $UDID."
382 | fi
383 | else
384 | # No user currently logged in
385 | echo "$stamp Info: No users logged in on MachineID: $UDID."
386 | fi
387 |
388 | exit
389 | }
390 |
391 | ####################################################################################################
392 | # DEMOTE THE USER #
393 |
394 | # Use funciton to demote user
395 | demoteUser
--------------------------------------------------------------------------------
/pkg/PrivilegesDemoter.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 | #Version:3.0
3 |
4 | ####################################################################################################
5 | # MUST BE RUN AS ROOT #
6 |
7 | if [ "$EUID" -ne 0 ]; then
8 | echo "This script must be run as root!"
9 | exit
10 | fi
11 |
12 | ####################################################################################################
13 | # LOG SETUP #
14 |
15 | # All privilege events logging location
16 | privilegesLog="/var/log/privileges.log"
17 |
18 | # Check if log exists and create if needed
19 | if [ ! -f "$privilegesLog" ]; then
20 | touch "$privilegesLog"
21 | fi
22 |
23 | # Function for logging privileges demoter actions
24 | pdLog () {
25 | # Create stamp for privileges demoter logging
26 | stamp="$(date +"%Y-%m-%d %H:%M:%S%z") blog.mostlymac.privileges.demoter"
27 |
28 | # Redirect to log file
29 | echo "$stamp $1" >> "$privilegesLog"
30 | }
31 |
32 | ####################################################################################################
33 | # SET SCRIPT VARIABLES #
34 |
35 | # PrivilegesDemoter managed preferences plist
36 | pdPrefs="/Library/Managed Preferences/blog.mostlymac.privilegesdemoter.plist"
37 |
38 | if [[ -e "$pdPrefs" ]]; then
39 | # Get help button status
40 | help_button_status="$( /usr/libexec/PlistBuddy -c "print helpButton:helpButtonStatus" "$pdPrefs" 2>/dev/null )"
41 |
42 | # Get the help button type
43 | help_button_type="$( /usr/libexec/PlistBuddy -c "print helpButton:helpButtonType" "$pdPrefs" 2>/dev/null )"
44 |
45 | # Get the help button payload
46 | help_button_payload="$( /usr/libexec/PlistBuddy -c "print helpButton:helpButtonPayload" "$pdPrefs" 2>/dev/null )"
47 |
48 | # Get notification sound setting
49 | notification_sound="$( /usr/libexec/PlistBuddy -c "print ibmNotifierSettings:notificationSound" "$pdPrefs" 2>/dev/null )"
50 |
51 | # Are we using IBM Notifer?
52 | ibm_notifier="$( /usr/libexec/PlistBuddy -c "print notificationAgent:ibmNotifier" "$pdPrefs" 2>/dev/null )"
53 |
54 | # Are we using Swift Dialog?
55 | swift_dialog="$( /usr/libexec/PlistBuddy -c "print notificationAgent:swiftDialog" "$pdPrefs" 2>/dev/null )"
56 |
57 | # Get list of excluded admins
58 | admin_to_exclude="$( /usr/libexec/PlistBuddy -c "print excludedAdmins" "$pdPrefs" 2>/dev/null )"
59 |
60 | # Get admin threshold
61 | admin_threshold="$( /usr/libexec/PlistBuddy -c "print reminderThreshold" "$pdPrefs" 2>/dev/null )"
62 |
63 | # Get silent operation setting
64 | silent="$( /usr/libexec/PlistBuddy -c "print notificationAgent:disableNotifications" "$pdPrefs" 2>/dev/null )"
65 |
66 | # Get setting for running from jamf
67 | jamf="$( /usr/libexec/PlistBuddy -c "print jamfProSettings:useJamfPolicy" "$pdPrefs" 2>/dev/null )"
68 |
69 | # Get setting for standalone mode without SAP Privileges
70 | standalone="$( /usr/libexec/PlistBuddy -c "print standaloneMode" "$pdPrefs" 2>/dev/null )"
71 |
72 | # Check for jamf trigger. Set to default if not found
73 | if [[ ! $( /usr/libexec/PlistBuddy -c "print jamfProSettings:jamfTrigger" "$pdPrefs" 2>/dev/null ) ]]; then
74 | jamf_trigger="privilegesDemote"
75 | else
76 | jamf_trigger="$( /usr/libexec/PlistBuddy -c "print jamfProSettings:jamfTrigger" "$pdPrefs" 2>/dev/null )"
77 | fi
78 |
79 | # Get main text for notifications. Set to default if not found
80 | if [[ ! $( /usr/libexec/PlistBuddy -c "print mainText" "$pdPrefs" 2>/dev/null ) ]]; then
81 | main_text=$( printf "You are currently an administrator on this device.\n\nIt is recommended to operate as a standard user whenever possible.\n\nDo you still require elevated privileges?" )
82 | else
83 | get_text="$( /usr/libexec/PlistBuddy -c "print mainText" "$pdPrefs" 2>/dev/null )"
84 | # Strip out extra slash in new line characters
85 | main_text=$( printf "${get_text//'\\n'/\n}" )
86 | fi
87 |
88 | # Check for IBM Notifier path. Set to default if not found
89 | if [[ ! $( /usr/libexec/PlistBuddy -c "print ibmNotifierSettings:ibmNotifierPath" "$pdPrefs" 2>/dev/null ) ]]; then
90 | ibm_notifier_path="/Applications/IBM Notifier.app"
91 | else
92 | ibm_notifier_path="$( /usr/libexec/PlistBuddy -c "print ibmNotifierSettings:ibmNotifierPath" "$pdPrefs" 2>/dev/null )"
93 | fi
94 |
95 | # Check for IBM Notifier custom binary name. Set to default if not found
96 | if [[ ! $( /usr/libexec/PlistBuddy -c "print ibmNotifierSettings:ibmNotifierBinary" "$pdPrefs" 2>/dev/null ) ]]; then
97 | ibm_notifier_binary="IBM Notifier"
98 | else
99 | ibm_notifier_binary="$( /usr/libexec/PlistBuddy -c "print ibmNotifierSettings:ibmNotifierBinary" "$pdPrefs" 2>/dev/null )"
100 | fi
101 | else
102 | main_text=$( printf "You are currently an administrator on this device.\n\nIt is recommended to operate as a standard user whenever possible.\n\nDo you still require elevated privileges?" )
103 | fi
104 |
105 | # Set path to the icon
106 | if [ -f /usr/local/mostlymac/icon.png ]; then
107 | icon="/usr/local/mostlymac/icon.png"
108 | elif [ -f /Applications/Privileges.app/Contents/Resources/AppIcon.icns ]; then
109 | icon="/Applications/Privileges.app/Contents/Resources/AppIcon.icns"
110 | else
111 | icon="/System/Library/CoreServices/CoreTypes.bundle/Contents/Resources/AlertNoteIcon.icns"
112 | fi
113 |
114 | # Set the default path to swift dialog
115 | swift_dialog_path="/usr/local/bin/dialog"
116 |
117 | # Get DockToggleTimeout from SAP Privileges preferences (if it exists)
118 | sapPrivilegesPreferences="/Library/Managed Preferences/corp.sap.privileges.plist"
119 | if [ -e "$sapPrivilegesPreferences" ]; then
120 | sapDockToggleTimeout=$( /usr/libexec/PlistBuddy -c "print DockToggleTimeout" "$sapPrivilegesPreferences" 2>/dev/null )
121 | fi
122 |
123 | # Log file which contains the timestamps of the last runs
124 | checkFile="/tmp/privilegesCheck"
125 |
126 | # Location of PrivilegesCLI
127 | privilegesCLI="/Applications/Privileges.app/Contents/Resources/PrivilegesCLI"
128 |
129 | ####################################################################################################
130 | # SET USER AND DEVICE INFO #
131 |
132 | # Get the current user
133 | currentUser=$( scutil <<< "show State:/Users/ConsoleUser" | awk '/Name :/ && ! /loginwindow/ { print $3 }' )
134 |
135 | # Get machine UDID
136 | UDID=$( ioreg -d2 -c IOPlatformExpertDevice | awk -F\" '/IOPlatformUUID/{print $(NF-1)}' )
137 |
138 | ####################################################################################################
139 | # FUNCTIONS #
140 |
141 | # Function to display help message with usage options
142 | usage () {
143 | echo ""
144 | echo " Usage: ./PrivilegesDemoter.sh [--options]"
145 | echo ""
146 | echo " [no flags] If the current user has passed the admin threshold, offer to demote them."
147 | echo " --elevate Elevate the current user to administrator"
148 | echo " --demote Demote the current user to standard"
149 | echo " --demote-silent Demote the current user to standard silently"
150 | echo " --status Displays the current user's privileges"
151 | echo " --admin-time Display elapsed time since last PrivilegesDemoter run"
152 | echo " --help Display this message"
153 | echo ""
154 |
155 | exit
156 | }
157 |
158 | # Function to get elapsed time since last run
159 | adminTime () {
160 | if /usr/sbin/dseditgroup -o checkmember -m "$currentUser" admin &> /dev/null; then
161 | # If there is no checkfile explain why, then creat it.
162 | if [[ ! -f ${checkFile} ]]; then
163 | echo "PrivilegesDemoter has not run since the last elevation. Initializing timer now..."
164 | echo "Note: PrivilegesDemoter only runs once every 5 minutes."
165 | # Use function to initiate timestamp
166 | initTimestamp
167 | else
168 | # Use function to initiate timestamp
169 | initTimestamp
170 | fi
171 |
172 | # Get the start time
173 | startTime=$( head -n 1 "$checkFile" )
174 |
175 | # Get the elapsed time
176 | elapsedTime=$((timeStamp - startTime))
177 |
178 | convertAndPrintSeconds() {
179 | local totalSeconds=$1;
180 | local seconds=$((totalSeconds%60));
181 | local minutes=$((totalSeconds/60%60));
182 | (( minutes > 0 )) && printf '%d minutes ' $minutes;
183 | printf '%d seconds\n' $seconds;
184 | }
185 |
186 | # Convert to human readable format
187 | convertAndPrintSeconds "$elapsedTime"
188 | else
189 | # User is not admin.
190 | echo "$currentUser is not an administrator. Nothing to do."
191 | fi
192 |
193 | exit
194 | }
195 |
196 | # Function to elevate the current user
197 | elevate () {
198 | if [[ "$standalone" = true ]] || [[ ! -e "${privilegesCLI}" ]]; then
199 | if /usr/sbin/dseditgroup -o checkmember -m "$currentUser" admin &> /dev/null; then
200 | pdLog "Status: User $currentUser already has the requested privileges. Nothing to do."
201 | echo "$currentUser is already an administrator. Nothing to do."
202 | else
203 | /usr/sbin/dseditgroup -o edit -a "$currentUser" -t user admin
204 | initTimestamp
205 | pdLog "Status: $currentUser is now an admin user on MachineID: $UDID."
206 | echo "$currentUser now has administrator rights."
207 | fi
208 | else
209 | launchctl asuser "$currentUserID" sudo -u "$currentUser" "$privilegesCLI" --add
210 | initTimestamp
211 | fi
212 |
213 | exit
214 | }
215 |
216 | # Function to demote the current user
217 | demote () {
218 | if [[ "$standalone" = true ]] || [[ ! -e "${privilegesCLI}" ]]; then
219 | /usr/sbin/dseditgroup -o edit -d "$currentUser" -t user admin
220 | else
221 | launchctl asuser "$currentUserID" sudo -u "$currentUser" "$privilegesCLI" --remove &> /dev/null
222 | fi
223 | }
224 |
225 | # Function to get the current user status
226 | status () {
227 | if [[ "$standalone" = true ]] || [[ ! -e "${privilegesCLI}" ]]; then
228 | if /usr/sbin/dseditgroup -o checkmember -m "$currentUser" admin &> /dev/null; then
229 | echo "User $currentUser has administrator rights."
230 | else
231 | echo "User $currentUser has standard user rights."
232 | fi
233 | else
234 | launchctl asuser "$currentUserID" sudo -u "$currentUser" "$privilegesCLI" --status
235 | fi
236 |
237 | exit
238 | }
239 |
240 |
241 | # Function to get the last 5 minutes of logs from SAP privileges helper
242 | sapPrivilegesLogger () {
243 | # The Privileges.app elevation event is not logged elsewhere, so this should capture it
244 | privilegesHelperLog=$( log show --style compact --predicate 'process == "corp.sap.privileges.helper"' --last 5m | grep "SAPCorp" )
245 |
246 | # Check if elevation event exists and add it to the log file
247 | if [ "$privilegesHelperLog" ]; then
248 | echo "$privilegesHelperLog" | while read -r line; do echo "${line} on MachineID: $UDID" >> $privilegesLog; done
249 | fi
250 | }
251 |
252 | # Function to initiate timestamp for admin calculations
253 | initTimestamp () {
254 | # Get current timestamp
255 | timeStamp=$(date +%s)
256 |
257 | # Check if log file exists and create if needed
258 | if [[ ! -f ${checkFile} ]]; then
259 | # Create file with current timestamp
260 | touch ${checkFile}
261 | echo "${timeStamp}" > ${checkFile}
262 | fi
263 | }
264 |
265 | # Function to confirm privileges have been changed successfully and log error after 1 retry.
266 | # Takes user as argument $1
267 | confirmPrivileges () {
268 |
269 | # If user is still admin, try revoking again using dseditgroup
270 | if /usr/sbin/dseditgroup -o checkmember -m "${1}" admin &> /dev/null; then
271 | pdLog "Warn: ${1} is still an admin on MachineID: $UDID. Trying again..."
272 | /usr/sbin/dseditgroup -o edit -d "${1}" -t user admin
273 | sleep 1
274 |
275 | # If user was not sucessfully demoted after retry, write error to log. Otherwise log success.
276 | if /usr/sbin/dseditgroup -o checkmember -m "${1}" admin &> /dev/null; then
277 | pdLog "Error: Could not demote ${1} to standard on MachineID: $UDID."
278 | else
279 | # Successfully demoted with dseditgroup
280 | # If dock is running and not in standalone mode, reload to display correct tile
281 | if [[ $(/usr/bin/pgrep Dock) -gt 0 ]] && [[ "$standalone" != true ]]; then
282 | /usr/bin/killall Dock
283 | fi
284 |
285 | # Log that user was successfully demoted.
286 | pdLog "Status: ${1} is now a standard user on MachineID: $UDID."
287 | fi
288 |
289 | else
290 | # Log that user was successfully demoted.
291 | pdLog "Status: ${1} is now a standard user on MachineID: $UDID."
292 | fi
293 |
294 | # Clean up privileges check log file to reset timer
295 | rm "$checkFile" &> /dev/null
296 | }
297 |
298 | # Function to prompt with IBM Notifier
299 | prompt_with_ibmNotifier () {
300 |
301 | # If help button is enabled, set type and payload
302 | if [[ $help_button_status = true ]]; then
303 | help_info=("-help_button_cta_type" "${help_button_type}" "-help_button_cta_payload" "${help_button_payload}")
304 | fi
305 |
306 | # Disable sounds if needed
307 | if [[ $notification_sound = false ]]; then
308 | sound=("-silent")
309 | fi
310 |
311 | # Prompt the user
312 | prompt_user() {
313 | button=$( "${ibm_notifier_path}/Contents/MacOS/${ibm_notifier_binary}" \
314 | -type "popup" \
315 | -bar_title "Privileges Reminder" \
316 | -subtitle "$main_text" \
317 | -icon_path "$icon" \
318 | -main_button_label "No" \
319 | -secondary_button_label "Yes" \
320 | "${help_info[@]}" \
321 | -timeout 120 \
322 | "${sound[@]}" \
323 | -position center \
324 | -always_on_top )
325 |
326 | echo "$?"
327 | }
328 |
329 | # Get the user's response
330 | buttonClicked=$( prompt_user )
331 | }
332 |
333 | # Function to prompt with Swift Dialog
334 | prompt_with_swiftDialog () {
335 |
336 | # If help button is enabled, set message and payload accordingly
337 | if [[ $help_button_status = true ]]; then
338 | if [[ $help_button_type == "infopopup" ]]; then
339 | help_info=("--helpmessage" "$help_button_payload")
340 | elif [[ $help_button_type == "link" ]]; then
341 | help_info=("--infobuttontext" "More Info" "--infobuttonaction" "$help_button_payload")
342 | fi
343 | fi
344 |
345 | # Prompt the user
346 | prompt_user() {
347 | button=$( "${swift_dialog_path}" \
348 | --title none \
349 | --message "$main_text" \
350 | --messagefont size=15 \
351 | --icon "$icon" \
352 | --iconsize 75 \
353 | --button1text No \
354 | --button2text Yes \
355 | "${help_info[@]}" \
356 | --timer 120 \
357 | --height 180 \
358 | --width 520 \
359 | --ontop )
360 |
361 | echo "$?"
362 | }
363 |
364 | # Get the user's response
365 | buttonClicked=$( prompt_user )
366 | }
367 |
368 | # Function to prompt with Jamf Helper
369 | prompt_with_jamfHelper () {
370 |
371 | # Prompt the user
372 | prompt_user() {
373 | button=$( "/Library/Application Support/JAMF/bin/jamfHelper.app/Contents/MacOS/jamfHelper" \
374 | -windowType utility \
375 | -title "Privileges Reminder" \
376 | -description "$main_text" \
377 | -alignDescription left \
378 | -icon "$icon" \
379 | -button1 No \
380 | -button2 Yes \
381 | -defaultButton 1 \
382 | -timeout 120 )
383 |
384 | echo "$?"
385 | }
386 |
387 | # Get the user's response
388 | buttonClicked=$( prompt_user )
389 | }
390 |
391 | # Function to check if array contains a user
392 | containsUser () {
393 | local e
394 | for e in "${@:2}"; do [[ "$e" == "$1" ]] && return 1; done
395 | return 0
396 | }
397 |
398 | # Function to perform admin user demotion
399 | demoteUser () {
400 | # Check for a logged in user
401 | if [[ $currentUser != "" ]]; then
402 |
403 | # If jamf is set to true, try using a jamf policy
404 | if [[ "$jamf" = true ]]; then
405 | # Check that Jamf Pro is available
406 | if /usr/local/bin/jamf checkJSSConnection -retry 1 &> /dev/null; then
407 | # Jamf is available. Call the jamf policy by trigger and exit
408 | /usr/local/bin/jamf policy -event "$jamf_trigger"
409 | exit 0
410 | else
411 | # Jamf is not available. Log and continue with local demotion.
412 | pdLog "Error: Jamf Pro Server could not be reached. Continuing locally..."
413 | fi
414 | fi
415 |
416 | # Get the current user's UID
417 | currentUserID=$(id -u "$currentUser")
418 |
419 | # If current user is an admin, remove rights
420 | if /usr/sbin/dseditgroup -o checkmember -m "$currentUser" admin &> /dev/null; then
421 |
422 | # Read comma separated list of excluded admins into array
423 | IFS=', ' read -r -a excludedUsers <<< "$admin_to_exclude"
424 |
425 | # Add always excluded users to array
426 | excludedUsers+=("root" "_mbsetupuser")
427 |
428 | # Use function to check if current user is excluded from demotion
429 | containsUser "$currentUser" "${excludedUsers[@]}"
430 | excludedUserLoggedIn="$?"
431 |
432 | # If current user is excluded from demotion, reset timer and exit
433 | if [[ "$excludedUserLoggedIn" = 1 ]]; then
434 | pdLog "Info: Excluded admin user $currentUser logged in on MachineID: $UDID. Will not perform demotion."
435 | # Reset timer and exit 0
436 | rm "$checkFile" &> /dev/null
437 | exit 0
438 | fi
439 |
440 | # User with admin is logged in.
441 | # If silent option is passed, demote silently
442 | if [[ $silent = true ]]; then
443 | # Revoke rights silently
444 | pdLog "Info: Silent option used. Removing rights for $currentUser on MachineID: $UDID without notification."
445 | # Use function to demote user
446 | demote
447 |
448 | # Run confirm privileges function with current user.
449 | confirmPrivileges "$currentUser"
450 |
451 | exit
452 |
453 | else
454 | # Notify the user. Use app that user defined and fall back jamf helper
455 | if [[ $ibm_notifier = true ]]; then
456 | if [[ -e "${ibm_notifier_path}" ]]; then
457 | prompt_with_ibmNotifier
458 | else
459 | pdLog "Warn: IBM Notifier not found. Defaulting to Jamf Helper for notification."
460 | prompt_with_jamfHelper
461 | fi
462 | elif [[ $swift_dialog = true ]]; then
463 | if [[ -e "${swift_dialog_path}" ]]; then
464 | prompt_with_swiftDialog
465 | else
466 | pdLog "Warn: Swift Dialog not found. Defaulting to Jamf Helper for notification."
467 | prompt_with_jamfHelper
468 | fi
469 | else
470 | prompt_with_jamfHelper
471 | fi
472 | fi
473 |
474 | # If the user clicked NO (button 0), remove admin rights immediately
475 | if [[ $buttonClicked = 0 ]]; then
476 | # Revoke rights
477 | pdLog "Decision: $currentUser no longer needs admin rights. Removing rights on MachineID: $UDID now."
478 | # Use function to demote user
479 | demote
480 |
481 | # Run confirm privileges function with current user.
482 | confirmPrivileges "$currentUser"
483 |
484 | # If the user clicked YES (button 2) leave admin rights in tact
485 | elif [[ $buttonClicked = 2 ]]; then
486 | pdLog "Decision: $currentUser says they still need admin rights on MachineID: $UDID."
487 | pdLog "Status: Resetting timer and allowing $currentUser to remain an admin on MachineID: $UDID."
488 |
489 | # Clean up privileges check file to reset timer
490 | rm "$checkFile" &> /dev/null
491 |
492 | # Restart the timer immidiately
493 | initTimestamp
494 |
495 | # If timeout occurred, (exit code 4) remove admin rights
496 | elif [[ $buttonClicked = 4 ]]; then
497 | pdLog "Decision: Timeout occurred. Removing admin rights for $currentUser on MachineID: $UDID now."
498 | # Use function to demote user
499 | demote
500 |
501 | # Run confirm privileges function with current user.
502 | confirmPrivileges "$currentUser"
503 |
504 | # If unexpected code is returned, log an error
505 | else
506 | pdLog "Error: Unexpected exit code [$buttonClicked] returned from prompt. User: $currentUser, MachineID: $UDID."
507 | fi
508 | else
509 | # Current user is not an admin
510 | pdLog "Info: $currentUser is not an admin on MachineID: $UDID."
511 | fi
512 | else
513 | # No user currently logged in
514 | pdLog "Info: No users logged in on MachineID: $UDID."
515 | fi
516 |
517 | exit
518 | }
519 |
520 | # Function to check if admin time threshold is passed
521 | checkAdminThreshold () {
522 | # Check if user is an admin
523 | if /usr/sbin/dseditgroup -o checkmember -m "$currentUser" admin &> /dev/null; then
524 | # Process admin time
525 | oldTimeStamp=$(head -1 "${checkFile}")
526 | echo "${timeStamp}" >> "${checkFile}"
527 |
528 | adminTime=$((timeStamp - oldTimeStamp))
529 |
530 | # If user is admin for more than the time limit, return true
531 | if [[ ${adminTime} -ge ${timeLimit} ]]; then
532 | return 1
533 | fi
534 | else
535 | # User is not admin. Return false, and reset timer
536 | rm "${checkFile}"
537 | return 0
538 | fi
539 | }
540 |
541 | ####################################################################################################
542 | # GET INPUTS #
543 |
544 | # Get inputs
545 | while test $# -gt 0; do
546 | case "$1" in
547 | --elevate)
548 | # Run function to elevate the user now
549 | elevate
550 | ;;
551 | --demote)
552 | # Run function to demote the user now
553 | demoteUser
554 | ;;
555 | --demote-silent)
556 | # Set the silent flag to true
557 | silent=true
558 | # Run function to demote the user now
559 | demoteUser
560 | ;;
561 | --admin-time)
562 | # Run function to display how long user has had admin rights
563 | adminTime
564 | ;;
565 | --status)
566 | # Run function to display user status
567 | status
568 | ;;
569 | --help|-*)
570 | # Display usage dialog
571 | usage
572 | ;;
573 | esac
574 | shift
575 | done
576 |
577 | ####################################################################################################
578 | # THRESHOLD SETUP #
579 |
580 | # Check for PrivilegesDemoter admin_threshold or SAP Privileges DockTileTimeout (in that order)
581 | # If keys are not present or set to 0, use default value of 15 minutes
582 | if [ "$admin_threshold" ] && [ "$admin_threshold" != 0 ]; then
583 | timeLimit=$((admin_threshold * 60))
584 | elif [ "$sapDockToggleTimeout" ] && [ "$sapDockToggleTimeout" != 0 ]; then
585 | timeLimit=$((sapDockToggleTimeout * 60))
586 | else
587 | timeLimit=900
588 | fi
589 |
590 | ####################################################################################################
591 | # DO THE WORK #
592 |
593 | # Use function to initiate timestamp
594 | initTimestamp
595 |
596 | # Use function to get the last 5 minutes of logs from SAP privileges helper
597 | sapPrivilegesLogger
598 |
599 | # Use function to determine if logged in user has passed the threshold
600 | checkAdminThreshold
601 | passedThreshold="$?"
602 |
603 | # If logged in user is passed the threshold offer to demote them
604 | if [[ "$passedThreshold" = 1 ]]; then
605 | # Use function to demote user
606 | demoteUser
607 | fi
--------------------------------------------------------------------------------