Skip to content

Commit

Permalink
first pass poc at macos presence detection
Browse files Browse the repository at this point in the history
  • Loading branch information
James-Pickett committed Sep 13, 2024
1 parent 766d939 commit 9d05605
Show file tree
Hide file tree
Showing 2 changed files with 150 additions and 0 deletions.
102 changes: 102 additions & 0 deletions ee/presence/presence_darwin.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
package presence

/*
#cgo CFLAGS: -x objective-c -fmodules -fblocks
#cgo LDFLAGS: -framework CoreFoundation -framework LocalAuthentication -framework Foundation
#include <stdlib.h>
#include <stdio.h>
#import <LocalAuthentication/LocalAuthentication.h>
struct AuthResult {
bool success; // true for success, false for failure
char* error_msg; // Error message if any
int error_code; // Error code if any
};
struct AuthResult Authenticate(char const* reason) {
struct AuthResult authResult;
LAContext *myContext = [[LAContext alloc] init];
NSError *authError = nil;
dispatch_semaphore_t sema = dispatch_semaphore_create(0);
NSString *nsReason = [NSString stringWithUTF8String:reason];
__block bool success = false;
__block NSString *errorMessage = nil;
__block int errorCode = 0;
// Use LAPolicyDeviceOwnerAuthentication to allow biometrics and password fallback
if ([myContext canEvaluatePolicy:LAPolicyDeviceOwnerAuthentication error:&authError]) {
[myContext evaluatePolicy:LAPolicyDeviceOwnerAuthentication
localizedReason:nsReason
reply:^(BOOL policySuccess, NSError *error) {
if (policySuccess) {
success = true; // Authentication successful
} else {
success = false;
errorCode = (int)[error code];
errorMessage = [error localizedDescription];
if (error.code == LAErrorUserFallback || error.code == LAErrorAuthenticationFailed) {
// Prompting for password
[myContext evaluatePolicy:LAPolicyDeviceOwnerAuthentication
localizedReason:@"Please enter your password"
reply:^(BOOL pwdSuccess, NSError *error) {
if (pwdSuccess) {
success = true;
} else {
success = false;
errorCode = (int)[error code];
errorMessage = [error localizedDescription];
}
dispatch_semaphore_signal(sema);
}];
} else {
errorCode = (int)[error code];
errorMessage = [error localizedDescription];
}
}
dispatch_semaphore_signal(sema);
}];
} else {
success = false; // Cannot evaluate policy
errorCode = (int)[authError code];
errorMessage = [authError localizedDescription];
}
dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER);
dispatch_release(sema);
authResult.success = success;
authResult.error_code = errorCode;
if (errorMessage != nil) {
authResult.error_msg = strdup([errorMessage UTF8String]); // Copy error message to C string
} else {
authResult.error_msg = NULL;
}
return authResult;
}
*/
import "C"
import (
"fmt"
"unsafe"
)

func detect(reason string) (bool, error) {
reasonStr := C.CString(reason)
defer C.free(unsafe.Pointer(reasonStr))

result := C.Authenticate(reasonStr)

// Convert C error message to Go string
if result.error_msg != nil {
defer C.free(unsafe.Pointer(result.error_msg))
}
errorMessage := C.GoString(result.error_msg)

// Return success or failure, with an error if applicable
if result.success {
return true, nil
}

return false, fmt.Errorf("authentication failed: %d %s", int(result.error_code), errorMessage)
}
48 changes: 48 additions & 0 deletions ee/presence/presence_darwin_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,48 @@
package presence

import (
"os"
"testing"

"github.com/stretchr/testify/assert"
"github.com/stretchr/testify/require"
)

const testPresenceEnvVar = "launcher_test_presence"

// Since there is no way to test user presence in a CI / automated fashion,
// these test are expected to be run manually via cmd line when needed.

// To test this run
//
// launcher_test_presence=true go test ./ee/presence/ -run Test_biometricDetectSuccess
//
// then successfully auth with the pop up
func Test_biometricDetectSuccess(t *testing.T) {
t.Parallel()

if os.Getenv(testPresenceEnvVar) == "" {
t.Skip("Skipping test_biometricDetectSuccess")
}

success, err := detect("IS TRYING TO TEST SUCCESS, PLEASE AUTHENTICATE")
require.NoError(t, err, "should not get an error on successful detect")
assert.True(t, success, "should be successful")
}

// To test this run
//
// launcher_test_presence=true go test ./ee/presence/ -run Test_biometricDetectCancel
//
// then cancel the biometric auth that pops up
func Test_biometricDetectCancel(t *testing.T) {
t.Parallel()

if os.Getenv(testPresenceEnvVar) == "" {
t.Skip("Skipping test_biometricDetectCancel")
}

success, err := detect("IS TRYING TO TEST CANCEL, PLEASE PRESS CANCEL")
require.Error(t, err, "should get an error on failed detect")
assert.False(t, success, "should not be successful")
}

0 comments on commit 9d05605

Please sign in to comment.