top of page
Chris Schasse

You down with TCC?

This is the follow up resources for our presentation "You Down With TCC" at the 2023 Penn State Mac Admins Conference. These files can be used during the presentation to follow along, or after the presentation to implement this workflow in your environment.


Workshop Files

In order to setup the workflow shown in this presentation, you will need the following files:

You will also need to download the DEPNotify package, which is located here: https://files.nomad.menu/DEPNotify.pkg



What is TCC?

TCC was introduced with macOS Mojave and stands for Transparency, Consent, and Control. This allows the user to have control over what applications can access, including Location Services, Bluetooth, and Full Disk Access, to name a few. However, the three we are focusing on for this presentation is camera, microphone, and screen recording, since these three cannot be controlled via an MDM.


Issues with managing TCC

  • Camera, Microphone, and Screen Recording are protected by SIP

  • There is no way to programmatically enable this for users

  • You can, however, guide users through this process

Guiding the User

  • Script with loops detecting if user has completed the action

  • Videos guiding users through steps using DEPNotify

  • Launching the application and/or System Settings

Example of the Workflow

Under the Hood

There are two places where information is stored about the computer's TCC policies:

  • /Library/Application Support/com.apple.TCC/TCC.db

    • Screen Recording

  • ~/Library/Application Support/com.apple.TCC/TCC.db

    • Camera

    • Microphone

The following commands explain how to read from the TCC database.

Show the TCC Database

sqlite3 -header "/Library/Application Support/com.apple.TCC/TCC.db" "select * from access"

Show just the services in the TCC Database

sqlite3 -header "/Library/Application Support/com.apple.TCC/TCC.db" "select distinct service from access"

Show just the applications that we have access information for

sqlite3 -header "/Library/Application Support/com.apple.TCC/TCC.db" "select distinct client from access"

Isolate the Client, Service, and Auth Value

sqlite3 -header "/Library/Application Support/com.apple.TCC/TCC.db" "select client, service, auth_value from access"

Just show the Client, Service and Auth Value for Screen Recording

service='screen'
sqlite3 -header "/Library/Application Support/com.apple.TCC/TCC.db" "select client, service, auth_value from access where service like '%${service}%'"

Just show the Client, Service and Auth Value for Screen Recording and Zoom

service='screen'
client='zoom'
sqlite3 -header "/Library/Application Support/com.apple.TCC/TCC.db" "select client, service, auth_value from access where service like '%${service}%' and client like '%${client}%'"

Lets see what the TCC Database in the User's Library looks like

sqlite3 -header "/Users/schasta/Library/Application Support/com.apple.TCC/TCC.db" "select * from access"

Lets look at the Zoom application specifically

service='microphone'
client='zoom'
sqlite3 -header "/Users/schasta/Library/Application Support/com.apple.TCC/TCC.db" "select client, service, auth_value from access where service like '%${service}%' and client like '%${client}%'"

Taking a look at our functions

We utilize functions pretty heavily in all of our scripts. Lets take a look at them one at a time to break down what's happening:


checkAccess function

  • The first variable defined is the application (EG 'zoom')

  • The second variable defined is the service (EG 'microphone')

  • Depending on the service, defines which TCC.db it's accessing

  • Reads the database using the first two variables defined

  • Returns the value "On" or "Off"

function checkAccess() {
	application=$1
	service=$2
	
	## Determine which database we need
	case ${service} in
		## Screen recording is handled at the /Library layer
		screen*)
			service='screen'
			db="/Library/Application Support/com.apple.TCC/TCC.db"
		;;
			
		## Microphone and camera are handled at the /User/Library layer
		'microphone' | 'camera')
			db="/Users/${CONFIG[currentuser]}/Library/Application Support/com.apple.TCC/TCC.db"
		;;
	esac
	
	query=""
	## Read the sqlite database
	access=$(sqlite3 \
		-separator ',' \
		"${db}" \
		"select client,service,auth_value from access where service like '%${service}%' and client like '%${application}%'"; \
	)

	lookupSuccess=$?	
	if [[ ${lookupSuccess} ]]; then
		## Send back "On" or "Off"
		[[ ${access: -1} -gt 0 ]] && echo "On" || echo "Off"
	fi
}

countdown function

  • Creates a countdown timer so that when we run a while loop to wait for the user to do something it doesn't run indefinitely.

  • The time it takes to timeout is dictated by the timeout function defined by a parameter

function countdown() { #d0919#
	## timeRemaining=$(countdown "${startTime}" "${CONFIG[timeout]}")
	
	## Input
	local startTime=$1 ## The UNIX time (in seconds) the clock started
	                   ## Ex. startTime=$(date +%s)
	local timeout=$2   ## How many seconds are we waiting
	
	## Output
	local remaining=0  ## Number of seconds left in the countdown
	
	remaining=$((${startTime}+${timeout}-$(date +%s)))
	echo ${remaining}
}

waitForAccess function

  • The first variable defined is the Access Type, which is also used for the Service in the checkAccess function

  • Creates a while loop that utilizes the checkAccess function and the countdown function to check if the user has completed the action

function waitForAccess() {
	## Input
	local accessType=$1
	
	## Output
	local statusCode=1 ## Let's assume there was a problem
	
	## Initial states
	local timeRemaining=${CONFIG[timeout]}
	local accessEnabled="Off"
	
	## Start the clock
	local startTime=$(date +%s)


	while [[ ${accessEnabled} == "Off" && ${timeRemaining} -gt 0 ]]; do
		sleep 1
		accessEnabled=$(checkAccess "${CONFIG[appcode]}" "${accessType}")
		timeRemaining=$(countdown ${startTime} ${CONFIG[timeout]})
	done
	
	## Did we get here because it turned on or timeout
	if [[ ${accessEnabled} == "On" ]]; then
		statusCode=0
	else
		dumpLog
		cleanUp "ERROR: Timeout while enabling ${accessType}"
	fi
	
	return ${statusCode}
}

Basic way this is run

With these functions in place, you can begin to see how this is run using a small amount of code:

## Camera
DEPNotify "Status: Enable Camera"
DEPNotify "Command: MainText: Please enable the Camera for Zoom to continue."
waitForAccess "camera"

## Microphone
DEPNotify "Command: MainText: Please enable the Microphone for Zoom to continue."
DEPNotify "Status: Enable Microphone"
waitForAccess "microphone"

## Screen Sharing
DEPNotify "Command: MainText: Please enable Screen Sharing for Zoom to continue."
DEPNotify "Status: Enable Screen Sharing"
waitForAccess "screen"

GitHub

We'd love to hear your feedback and help improve this workflow! We will be updating this workflow over the next couple of months to support Swift Dialogue and allow for additional configuration through attributes. We'd also like to create more videos for more applications to give admins as many tools as possible. Join our GitHub repo and help add to this workflow! https://github.com/Rocketman-Tech/App-Setup-Helper



645 views2 comments

2 Comments


Do you have any updates on this workflow using swiftDialog?

Like

Jared Nay
Jared Nay
Sep 14, 2023

What would need to be changed to make the zsh script work with Addigy? I noticed that it currently has jamf requirements, which makes sense. But our org is on Addigy...

Like
bottom of page