mirror of
https://github.com/netbirdio/netbird.git
synced 2026-08-25 09:01:29 +02:00
112 lines
4.3 KiB
Go
112 lines
4.3 KiB
Go
package elevate
|
|
|
|
import (
|
|
"errors"
|
|
"runtime"
|
|
"testing"
|
|
|
|
"github.com/stretchr/testify/assert"
|
|
"github.com/stretchr/testify/require"
|
|
)
|
|
|
|
// The framework has to load and the symbols have to resolve, or nothing else here
|
|
// means anything.
|
|
func TestSecurityFrameworkLoads(t *testing.T) {
|
|
require.NoError(t, load(), "Security.framework must open")
|
|
|
|
for name, fn := range map[string]any{
|
|
"AuthorizationCreate": authorizationCreate,
|
|
"AuthorizationExecuteWithPrivileges": authorizationExecuteWithPrivileges,
|
|
"AuthorizationFree": authorizationFree,
|
|
"fileno": fileno,
|
|
"fclose": fclose,
|
|
} {
|
|
assert.NotNil(t, fn, "%s must resolve", name)
|
|
}
|
|
}
|
|
|
|
// A request with no interaction allowed exercises the whole call — the rights and
|
|
// environment structs, and the OSStatus that comes back — without a dialog anybody
|
|
// has to answer. What the system decides is its business; that it decides at all is
|
|
// what this asserts.
|
|
func TestAuthorizationCreateWithoutInteraction(t *testing.T) {
|
|
if err := load(); err != nil {
|
|
t.Skipf("Security.framework did not open: %v", err)
|
|
}
|
|
|
|
var pinner runtime.Pinner
|
|
defer pinner.Unpin()
|
|
|
|
rights := itemSet(&pinner, authorizationItem{name: cString(&pinner, rightExecute)})
|
|
environment := itemSet(&pinner, promptItem(&pinner))
|
|
require.EqualValues(t, 1, rights.count, "the rights struct layout must match the C one")
|
|
|
|
var authorization uintptr
|
|
status := authorizationCreate(rights, environment, flagDefaults|flagExtendRights, &authorization)
|
|
|
|
switch status {
|
|
case errAuthorizationSuccess:
|
|
// Credentials were already cached for this session.
|
|
authorizationFree(authorization, flagDestroyRights)
|
|
case errAuthorizationDenied, errAuthorizationInteractionNotAllowed:
|
|
// The expected answers when nobody may be asked.
|
|
default:
|
|
require.Failf(t, "unknown OSStatus", "AuthorizationCreate returned %d, want a status we recognise", status)
|
|
}
|
|
}
|
|
|
|
// Asking with a right nobody has must not be mistaken for a declined prompt: the
|
|
// caller would report nothing at all.
|
|
func TestAuthorizeUnknownRightIsNotDeclined(t *testing.T) {
|
|
if err := load(); err != nil {
|
|
t.Skipf("Security.framework did not open: %v", err)
|
|
}
|
|
|
|
var pinner runtime.Pinner
|
|
defer pinner.Unpin()
|
|
|
|
rights := itemSet(&pinner, authorizationItem{name: cString(&pinner, "io.netbird.right.that.does.not.exist")})
|
|
|
|
var authorization uintptr
|
|
status := authorizationCreate(rights, nil, flagDefaults|flagExtendRights, &authorization)
|
|
if status == errAuthorizationSuccess {
|
|
authorizationFree(authorization, flagDestroyRights)
|
|
}
|
|
assert.NotEqual(t, int32(errAuthorizationSuccess), status, "a right that does not exist must not be granted")
|
|
}
|
|
|
|
func TestMechanismAvailable(t *testing.T) {
|
|
assert.True(t, mechanismAvailable(), "the trampoline exists on every macOS")
|
|
}
|
|
|
|
// The one-shot's report is what stands in for an exit status here, so a run that
|
|
// says nothing must not read as success.
|
|
func TestCheckApplied(t *testing.T) {
|
|
require.NoError(t, checkApplied(AppliedMarker+"\n"), "the report the one-shot prints")
|
|
require.NoError(t, checkApplied("some warning\n"+AppliedMarker+"\n"), "the report after other output")
|
|
|
|
assert.Error(t, checkApplied(""), "a run that printed nothing did not apply the change")
|
|
assert.Error(t, checkApplied("dyld: library not loaded\n"), "output that is not the report")
|
|
}
|
|
|
|
// A panic out of the FFI layer has to reach the caller as "no mechanism", which is
|
|
// the outcome that offers the user the command instead of taking the window down.
|
|
func TestGuardTurnsAPanicIntoUnavailable(t *testing.T) {
|
|
err := guard("pretending to call something", func() error {
|
|
panic("purego: signature it cannot map")
|
|
})
|
|
|
|
require.ErrorIs(t, err, ErrUnavailable, "a panic must read as a missing mechanism")
|
|
assert.Contains(t, err.Error(), "pretending to call something", "what panicked")
|
|
}
|
|
|
|
// guard wraps every darwin path, so what a caller switches on has to survive it.
|
|
func TestGuardPassesErrorsThrough(t *testing.T) {
|
|
sentinel := errors.New("the call itself failed")
|
|
assert.ErrorIs(t, guard("calling", func() error { return sentinel }), sentinel,
|
|
"the error it was given")
|
|
assert.ErrorIs(t, guard("calling", func() error { return ErrDeclined }), ErrDeclined,
|
|
"a declined prompt stays declined")
|
|
assert.NoError(t, guard("calling", func() error { return nil }), "a call that worked")
|
|
}
|