mirror of
https://github.com/gravitational/teleport.git
synced 2026-10-11 22:49:54 +00:00
iOS: Debug Serial Number (#69017)
* Move SQLite files into a SQLite subfolder * Add a debug-only user defaults store * Add a client for retrieving the device serial number In debug builds, the serial number is not available (because it's only available through MDM managed configs) so we fake it by reaching into the debug user defaults and storing something there. * Add a debug settings view This is where we can noodle/iterate on settings that only affect debug build behaviors. * Resolve some Xcode 27 warnings * Remove code signing from intermediate build products, deferring them to the final build * Update FakeSerialNumberGenerator doc comment (and change it to an enum)
This commit is contained in:
@@ -34,7 +34,6 @@ public enum EnrollClientError: Error, Sendable {
|
||||
case clientCreationFailed
|
||||
}
|
||||
|
||||
|
||||
extension EnrollClient {
|
||||
public static let liveValue = EnrollClient(
|
||||
requestEnrollmentToken: { hostName, port, pairingToken in
|
||||
@@ -53,4 +52,3 @@ extension EnrollClient {
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -266,7 +266,7 @@
|
||||
attributes = {
|
||||
BuildIndependentTargetsInParallel = 1;
|
||||
LastSwiftUpdateCheck = 2650;
|
||||
LastUpgradeCheck = 2650;
|
||||
LastUpgradeCheck = 2700;
|
||||
TargetAttributes = {
|
||||
0C94C8512FA232E600C13C9A = {
|
||||
CreatedOnToolsVersion = 26.4.1;
|
||||
@@ -651,6 +651,7 @@
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
BUILD_LIBRARY_FOR_DISTRIBUTION = YES;
|
||||
CODE_SIGN_IDENTITY = "";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
DEVELOPMENT_TEAM = K497G57PDJ;
|
||||
@@ -706,6 +707,7 @@
|
||||
isa = XCBuildConfiguration;
|
||||
buildSettings = {
|
||||
BUILD_LIBRARY_FOR_DISTRIBUTION = YES;
|
||||
CODE_SIGN_IDENTITY = "";
|
||||
CODE_SIGN_STYLE = Automatic;
|
||||
CURRENT_PROJECT_VERSION = 1;
|
||||
DEVELOPMENT_TEAM = K497G57PDJ;
|
||||
|
||||
+1
-1
@@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "2650"
|
||||
LastUpgradeVersion = "2700"
|
||||
version = "1.7">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "2650"
|
||||
LastUpgradeVersion = "2700"
|
||||
version = "1.7">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "2650"
|
||||
LastUpgradeVersion = "2700"
|
||||
version = "1.7">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
|
||||
@@ -24,4 +24,7 @@ extension DependencyValues {
|
||||
|
||||
@DependencyEntry(liveValue: DeviceTrustCredentialClient.liveValue)
|
||||
nonisolated var deviceTrustCredentialClient = DeviceTrustCredentialClient()
|
||||
|
||||
@DependencyEntry(liveValue: SerialNumberClient.liveValue)
|
||||
nonisolated var serialNumberClient = SerialNumberClient()
|
||||
}
|
||||
|
||||
@@ -0,0 +1,33 @@
|
||||
// Teleport
|
||||
// Copyright (C) 2026 Gravitational, Inc.
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see http://www.gnu.org/licenses/
|
||||
|
||||
#if DEBUG
|
||||
import Foundation
|
||||
|
||||
/// A small utility for generating a fake serial number, mostly to support the debug-only serial number setting.
|
||||
enum FakeSerialNumberGenerator {
|
||||
private static let base: String = "ABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
|
||||
|
||||
static func generate() -> String {
|
||||
func chunk() -> String {
|
||||
(0 ..< 4)
|
||||
.compactMap { _ in Self.base.randomElement() }
|
||||
.reduce(into: "") { $0.append($1) }
|
||||
}
|
||||
return "\(chunk())-\(chunk())"
|
||||
}
|
||||
}
|
||||
#endif
|
||||
@@ -0,0 +1,44 @@
|
||||
// Teleport
|
||||
// Copyright (C) 2026 Gravitational, Inc.
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see http://www.gnu.org/licenses/
|
||||
|
||||
import DependenciesMacros
|
||||
import Foundation
|
||||
import Sharing
|
||||
|
||||
@DependencyClient
|
||||
struct SerialNumberClient {
|
||||
var getDeviceSerialNumber: @Sendable () -> String = { "" }
|
||||
}
|
||||
|
||||
extension SerialNumberClient {
|
||||
static let liveValue = SerialNumberClient(
|
||||
getDeviceSerialNumber: {
|
||||
#if DEBUG
|
||||
@Shared(.debugStorage(.debugSerialNumber))
|
||||
var debugSerialNumber: String? = nil
|
||||
|
||||
guard let debugSerialNumber else {
|
||||
let newDebugSerialNumber = FakeSerialNumberGenerator.generate()
|
||||
$debugSerialNumber.withLock { $0 = newDebugSerialNumber }
|
||||
return newDebugSerialNumber
|
||||
}
|
||||
return debugSerialNumber
|
||||
#else
|
||||
fatalError("Production path has not yet been implemented. Requires messing around in Jamf.")
|
||||
#endif
|
||||
},
|
||||
)
|
||||
}
|
||||
@@ -0,0 +1,43 @@
|
||||
// Teleport
|
||||
// Copyright (C) 2026 Gravitational, Inc.
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see http://www.gnu.org/licenses/
|
||||
|
||||
#if DEBUG
|
||||
|
||||
import SwiftUI
|
||||
|
||||
struct DebugSettingsView: View {
|
||||
@Bindable
|
||||
var viewModel: DebugSettingsViewModel
|
||||
|
||||
var body: some View {
|
||||
Form {
|
||||
Section("Serial Number") {
|
||||
HStack {
|
||||
TextField("Serial Number", text: viewModel.debugSerialNumberBinding)
|
||||
.textFieldStyle(.roundedBorder)
|
||||
.frame(maxWidth: .infinity, alignment: .leading)
|
||||
Button {
|
||||
viewModel.regenerateSerialNumber()
|
||||
} label: {
|
||||
Image(systemName: "arrow.circlepath")
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,47 @@
|
||||
// Teleport
|
||||
// Copyright (C) 2026 Gravitational, Inc.
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see http://www.gnu.org/licenses/
|
||||
|
||||
#if DEBUG
|
||||
|
||||
import Dependencies
|
||||
import Foundation
|
||||
import Sharing
|
||||
import SwiftUI
|
||||
|
||||
@Observable @MainActor
|
||||
final class DebugSettingsViewModel {
|
||||
@ObservationIgnored
|
||||
@Dependency(\.serialNumberClient)
|
||||
var serialNumberClient
|
||||
|
||||
@ObservationIgnored
|
||||
@Shared(.debugStorage(.debugSerialNumber))
|
||||
var debugSerialNumber: String? = nil
|
||||
}
|
||||
|
||||
// MARK: - Serial Number
|
||||
|
||||
extension DebugSettingsViewModel {
|
||||
var debugSerialNumberBinding: Binding<String> {
|
||||
Binding($debugSerialNumber.emptyIfNil)
|
||||
}
|
||||
|
||||
func regenerateSerialNumber() {
|
||||
$debugSerialNumber.withLock { $0 = FakeSerialNumberGenerator.generate() }
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -30,11 +30,14 @@
|
||||
var body: some View {
|
||||
NavigationStack {
|
||||
List {
|
||||
Group {
|
||||
Section("Feature Demos") {
|
||||
Button("Secure Enclave Storage", systemImage: "lock") {
|
||||
viewModel.destination = .deviceTrustCredentialDemo(.init())
|
||||
}
|
||||
Section("Settings") {
|
||||
Button("Debug Settings", systemImage: "gearshape") {
|
||||
viewModel.destination = .debugSettingsView(.init())
|
||||
}
|
||||
}
|
||||
Section("Feature Demos") {
|
||||
Button("Secure Enclave Storage", systemImage: "lock") {
|
||||
viewModel.destination = .deviceTrustCredentialDemo(.init())
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -45,6 +48,9 @@
|
||||
.navigationDestination(item: $viewModel.destination.deviceTrustCredentialDemo) { viewModel in
|
||||
FeatureDemo.DeviceTrustCredentialView(viewModel: viewModel)
|
||||
}
|
||||
.navigationDestination(item: $viewModel.destination.debugSettingsView) { viewModel in
|
||||
DebugSettingsView(viewModel: viewModel)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -23,6 +23,10 @@
|
||||
final class DebugViewModel {
|
||||
@CasePathable
|
||||
enum Destination {
|
||||
// MARK: - Debug Settings
|
||||
|
||||
case debugSettingsView(DebugSettingsViewModel)
|
||||
|
||||
// MARK: - Feature Demos
|
||||
|
||||
case deviceTrustCredentialDemo(FeatureDemo.DeviceTrustCredentialViewModel)
|
||||
|
||||
@@ -47,7 +47,7 @@ struct LandingView: View {
|
||||
.background(Color.Background.depth3)
|
||||
|
||||
// MARK: Toolbar
|
||||
|
||||
|
||||
.toolbarVisibility(viewModel.shouldShowToolbar ? .visible : .hidden)
|
||||
.toolbar {
|
||||
ToolbarItem {
|
||||
@@ -101,7 +101,6 @@ struct LandingView: View {
|
||||
}
|
||||
#endif
|
||||
// swiftformat:enable indent
|
||||
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,52 @@
|
||||
// Teleport
|
||||
// Copyright (C) 2026 Gravitational, Inc.
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see http://www.gnu.org/licenses/
|
||||
|
||||
#if DEBUG
|
||||
import Foundation
|
||||
import Sharing
|
||||
|
||||
extension UserDefaults {
|
||||
static let debugSuiteName = "com.gravitational.teleport.verify.debug"
|
||||
|
||||
/// A UserDefaults suite that's only available in debug builds.
|
||||
///
|
||||
/// This is marked as `nonisolated(unsafe)` because UserDefaults isn't marked Sendable, but it _is_ thread safe.
|
||||
/// See <https://developer.apple.com/documentation/foundation/userdefaults>
|
||||
nonisolated(unsafe) static let debug: UserDefaults = {
|
||||
guard let store = UserDefaults(suiteName: debugSuiteName) else {
|
||||
preconditionFailure("Could not instantiate debug user defaults.")
|
||||
}
|
||||
return store
|
||||
}()
|
||||
}
|
||||
|
||||
// MARK: - Keys
|
||||
|
||||
/// Type-safe keys for use when querying the debug user defaults
|
||||
enum DebugUserDefaultsKey: String {
|
||||
case debugSerialNumber
|
||||
}
|
||||
|
||||
/// A small helper function to access the debug store via the Sharing library
|
||||
extension SharedReaderKey where Self == AppStorageKey<String?> {
|
||||
static func debugStorage(
|
||||
_ key: DebugUserDefaultsKey,
|
||||
) -> Self {
|
||||
.appStorage(key.rawValue, store: .debug)
|
||||
}
|
||||
}
|
||||
|
||||
#endif
|
||||
@@ -0,0 +1,29 @@
|
||||
// Teleport
|
||||
// Copyright (C) 2026 Gravitational, Inc.
|
||||
//
|
||||
// This program is free software: you can redistribute it and/or modify
|
||||
// it under the terms of the GNU Affero General Public License as published by
|
||||
// the Free Software Foundation, either version 3 of the License, or
|
||||
// (at your option) any later version.
|
||||
//
|
||||
// This program is distributed in the hope that it will be useful,
|
||||
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
// GNU Affero General Public License for more details.
|
||||
//
|
||||
// You should have received a copy of the GNU Affero General Public License
|
||||
// along with this program. If not, see http://www.gnu.org/licenses/
|
||||
|
||||
import Foundation
|
||||
|
||||
extension String? {
|
||||
var emptyIfNil: String {
|
||||
get {
|
||||
guard let self else { return "" }
|
||||
return self
|
||||
}
|
||||
set {
|
||||
self = newValue
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user