-
Notifications
You must be signed in to change notification settings - Fork 103
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
first pass poc at macos presence detection
- Loading branch information
1 parent
766d939
commit 9d05605
Showing
2 changed files
with
150 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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) | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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") | ||
} |