Download apps/shared/OpenClawKit/Tests/OpenClawKitTests/DeviceAuthStoreTests.swift from SaylorTwift/openclaw: direct link, hf CLI and curl.
- Browser
- Download file 21 kB
-
https://huggingface.co/SaylorTwift/openclaw/resolve/main/apps/shared/OpenClawKit/Tests/OpenClawKitTests/DeviceAuthStoreTests.swift
- Command line
-
hf download hf://SaylorTwift/openclaw/apps/shared/OpenClawKit/Tests/OpenClawKitTests/DeviceAuthStoreTests.swift
-
curl -L -o DeviceAuthStoreTests.swift https://huggingface.co/SaylorTwift/openclaw/resolve/main/apps/shared/OpenClawKit/Tests/OpenClawKitTests/DeviceAuthStoreTests.swift
21 kB
| import Foundation | |
| import SQLite3 | |
| import Testing | |
| @testable import OpenClawKit | |
| (.serialized) | |
| struct DeviceAuthStoreTests { | |
| (.stateDirectoryIsolated) | |
| func `store and load round trip without legacy files`() throws { | |
| let deviceID = "device-round-trip" | |
| #expect(DeviceAuthStore.storeTokenPersisted( | |
| deviceId: deviceID, | |
| role: " node ", | |
| token: "unscoped-token", | |
| scopes: [" write ", "read", "write", " "])) | |
| #expect(DeviceAuthStore.storeTokenPersisted( | |
| deviceId: deviceID, | |
| role: "operator", | |
| token: "scoped-token", | |
| scopes: ["zeta", "alpha"], | |
| gatewayID: "gateway-a")) | |
| #expect(try DeviceAuthStore.loadToken(deviceId: deviceID, role: "node") == DeviceAuthEntry( | |
| token: "unscoped-token", | |
| role: "node", | |
| scopes: ["read", "write"], | |
| updatedAtMs: #require(DeviceAuthStore.loadToken(deviceId: deviceID, role: "node")?.updatedAtMs))) | |
| let scoped = try #require(DeviceAuthStore.loadToken( | |
| deviceId: deviceID, | |
| role: "operator", | |
| gatewayID: "gateway-a")) | |
| #expect(scoped.role == "operator") | |
| #expect(scoped.gatewayID == "gateway-a") | |
| #expect(scoped.scopes == ["alpha", "zeta"]) | |
| for profile in [ | |
| GatewayDeviceIdentityProfile.primary, | |
| .node, | |
| .shareExtension, | |
| ] { | |
| #expect(try !FileManager.default.fileExists(atPath: Self.authURL(profile: profile).path)) | |
| } | |
| } | |
| (.stateDirectoryIsolated) | |
| func `same device id across profiles shares the token cache`() { | |
| let deviceID = "shared-device-id" | |
| // Matching device IDs imply matching key material and therefore one gateway device, | |
| // so profiles share this cache. The Node runtime reads device_auth_tokens only. | |
| #expect(DeviceAuthStore.storeTokenPersisted( | |
| deviceId: deviceID, | |
| role: "node", | |
| token: "primary-token", | |
| profile: .primary)) | |
| #expect(DeviceAuthStore.loadToken( | |
| deviceId: deviceID, | |
| role: "node", | |
| profile: .node)?.token == "primary-token") | |
| } | |
| (.stateDirectoryIsolated) | |
| func `distinct device ids remain disjoint across profiles`() { | |
| #expect(DeviceAuthStore.storeTokenPersisted( | |
| deviceId: "primary-device", | |
| role: "node", | |
| token: "primary-token", | |
| profile: .primary)) | |
| #expect(DeviceAuthStore.loadToken( | |
| deviceId: "node-device", | |
| role: "node", | |
| profile: .node) == nil) | |
| #expect(DeviceAuthStore.storeTokenPersisted( | |
| deviceId: "node-device", | |
| role: "node", | |
| token: "node-token", | |
| profile: .node)) | |
| #expect(DeviceAuthStore.loadToken( | |
| deviceId: "primary-device", | |
| role: "node", | |
| profile: .primary)?.token == "primary-token") | |
| #expect(DeviceAuthStore.loadToken( | |
| deviceId: "node-device", | |
| role: "node", | |
| profile: .node)?.token == "node-token") | |
| } | |
| (.stateDirectoryIsolated) | |
| func `legacy file imports once and reconstructs scoped metadata`() throws { | |
| let deviceID = "legacy-device" | |
| let gatewayID = "gateway-a" | |
| let scopedKey = "v2.\(Self.storageComponent(gatewayID)).\(Self.storageComponent("operator"))" | |
| try Self.writeLegacy(DeviceAuthStoreFile( | |
| version: 1, | |
| deviceId: deviceID, | |
| tokens: [ | |
| "node": DeviceAuthEntry( | |
| token: "legacy-node", | |
| role: "node", | |
| scopes: [" beta ", "alpha"], | |
| updatedAtMs: 100), | |
| scopedKey: DeviceAuthEntry( | |
| token: "legacy-operator", | |
| role: "operator", | |
| scopes: ["write"], | |
| updatedAtMs: 200, | |
| gatewayID: gatewayID), | |
| ])) | |
| #expect(DeviceAuthStore.loadToken(deviceId: deviceID, role: "node")?.token == "legacy-node") | |
| #expect(try !FileManager.default.fileExists(atPath: Self.authURL().path)) | |
| let scoped = try #require(DeviceAuthStore.loadToken( | |
| deviceId: deviceID, | |
| role: "operator", | |
| gatewayID: gatewayID)) | |
| #expect(scoped.token == "legacy-operator") | |
| #expect(scoped.role == "operator") | |
| #expect(scoped.gatewayID == gatewayID) | |
| #expect(scoped.scopes == ["write"]) | |
| #expect(DeviceAuthStore.loadToken(deviceId: deviceID, role: "node")?.scopes == ["alpha", "beta"]) | |
| } | |
| (.stateDirectoryIsolated) | |
| func `legacy import preserves a canonical SQLite row`() throws { | |
| let deviceID = "preserve-device" | |
| #expect(DeviceAuthStore.storeTokenPersisted( | |
| deviceId: deviceID, | |
| role: "node", | |
| token: "sqlite-token", | |
| scopes: ["sqlite-scope"])) | |
| try Self.writeLegacy(DeviceAuthStoreFile( | |
| version: 1, | |
| deviceId: deviceID, | |
| tokens: [ | |
| "node": DeviceAuthEntry( | |
| token: "legacy-token", | |
| role: "node", | |
| scopes: ["legacy-scope"], | |
| updatedAtMs: 1), | |
| ])) | |
| let loaded = try #require(DeviceAuthStore.loadToken(deviceId: deviceID, role: "node")) | |
| #expect(loaded.token == "sqlite-token") | |
| #expect(loaded.scopes == ["sqlite-scope"]) | |
| #expect(try !FileManager.default.fileExists(atPath: Self.authURL().path)) | |
| } | |
| (.stateDirectoryIsolated) | |
| func `failed legacy removal cannot commit a clear`() throws { | |
| let deviceID = "removal-failure-device" | |
| try Self.writeLegacy(DeviceAuthStoreFile( | |
| version: 1, | |
| deviceId: deviceID, | |
| tokens: [ | |
| "node": DeviceAuthEntry( | |
| token: "legacy-token", | |
| role: "node", | |
| scopes: [], | |
| updatedAtMs: 100), | |
| ])) | |
| let authURL = try Self.authURL() | |
| let identityDirectory = authURL.deletingLastPathComponent() | |
| try FileManager.default.setAttributes([.posixPermissions: 0o500], ofItemAtPath: identityDirectory.path) | |
| defer { | |
| try? FileManager.default.setAttributes([.posixPermissions: 0o700], ofItemAtPath: identityDirectory.path) | |
| } | |
| DeviceAuthStore.clearToken(deviceId: deviceID, role: "node") | |
| #expect(FileManager.default.fileExists(atPath: authURL.path)) | |
| try FileManager.default.setAttributes([.posixPermissions: 0o700], ofItemAtPath: identityDirectory.path) | |
| #expect(DeviceAuthStore.loadToken(deviceId: deviceID, role: "node")?.token == "legacy-token") | |
| #expect(!FileManager.default.fileExists(atPath: authURL.path)) | |
| } | |
| (.stateDirectoryIsolated) | |
| func `temporary legacy access failure remains retryable`() throws { | |
| let deviceID = "access-failure-device" | |
| try Self.writeLegacy(DeviceAuthStoreFile( | |
| version: 1, | |
| deviceId: deviceID, | |
| tokens: [ | |
| "node": DeviceAuthEntry( | |
| token: "legacy-token", | |
| role: "node", | |
| scopes: [], | |
| updatedAtMs: 100), | |
| ])) | |
| let authURL = try Self.authURL() | |
| let identityDirectory = authURL.deletingLastPathComponent() | |
| try FileManager.default.setAttributes([.posixPermissions: 0o000], ofItemAtPath: identityDirectory.path) | |
| defer { | |
| try? FileManager.default.setAttributes([.posixPermissions: 0o700], ofItemAtPath: identityDirectory.path) | |
| } | |
| #expect(!DeviceAuthStore.storeTokenPersisted( | |
| deviceId: deviceID, | |
| role: "operator", | |
| token: "must-not-persist")) | |
| try FileManager.default.setAttributes([.posixPermissions: 0o700], ofItemAtPath: identityDirectory.path) | |
| #expect(FileManager.default.fileExists(atPath: authURL.path)) | |
| #expect(DeviceAuthStore.loadToken(deviceId: deviceID, role: "node")?.token == "legacy-token") | |
| #expect(!FileManager.default.fileExists(atPath: authURL.path)) | |
| } | |
| (.stateDirectoryIsolated) | |
| func `failed invalid-file quarantine aborts the requested write`() throws { | |
| let authURL = try Self.authURL() | |
| let identityDirectory = authURL.deletingLastPathComponent() | |
| try FileManager.default.createDirectory(at: identityDirectory, withIntermediateDirectories: true) | |
| try Data([0xFF]).write(to: authURL) | |
| try FileManager.default.setAttributes([.posixPermissions: 0o500], ofItemAtPath: identityDirectory.path) | |
| defer { | |
| try? FileManager.default.setAttributes([.posixPermissions: 0o700], ofItemAtPath: identityDirectory.path) | |
| } | |
| #expect(!DeviceAuthStore.storeTokenPersisted( | |
| deviceId: "quarantine-device", | |
| role: "node", | |
| token: "must-not-persist")) | |
| #expect(FileManager.default.fileExists(atPath: authURL.path)) | |
| try FileManager.default.setAttributes([.posixPermissions: 0o700], ofItemAtPath: identityDirectory.path) | |
| #expect(DeviceAuthStore.storeTokenPersisted( | |
| deviceId: "quarantine-device", | |
| role: "node", | |
| token: "sqlite-token")) | |
| #expect(DeviceAuthStore.loadToken( | |
| deviceId: "quarantine-device", | |
| role: "node")?.token == "sqlite-token") | |
| } | |
| (.stateDirectoryIsolated) | |
| func `corrupt legacy file is quarantined and SQLite remains writable`() throws { | |
| let authURL = try Self.authURL() | |
| try FileManager.default.createDirectory( | |
| at: authURL.deletingLastPathComponent(), | |
| withIntermediateDirectories: true) | |
| try Data([0xFF, 0x00, 0xAA]).write(to: authURL) | |
| #expect(DeviceAuthStore.storeTokenPersisted( | |
| deviceId: "corrupt-device", | |
| role: "node", | |
| token: "sqlite-token")) | |
| #expect(DeviceAuthStore.loadToken( | |
| deviceId: "corrupt-device", | |
| role: "node")?.token == "sqlite-token") | |
| #expect(!FileManager.default.fileExists(atPath: authURL.path)) | |
| let quarantined = try FileManager.default.contentsOfDirectory( | |
| atPath: authURL.deletingLastPathComponent().path) | |
| .filter { $0.hasPrefix("device-auth.json.invalid-") } | |
| #expect(quarantined.count == 1) | |
| } | |
| (.stateDirectoryIsolated) | |
| func `clear token distinguishes one scope from every scope`() { | |
| let deviceID = "clear-device" | |
| _ = DeviceAuthStore.storeToken(deviceId: deviceID, role: "node", token: "unscoped") | |
| _ = DeviceAuthStore.storeToken( | |
| deviceId: deviceID, | |
| role: "node", | |
| token: "gateway-a", | |
| gatewayID: "gateway-a") | |
| _ = DeviceAuthStore.storeToken( | |
| deviceId: deviceID, | |
| role: "node", | |
| token: "gateway-b", | |
| gatewayID: "gateway-b") | |
| _ = DeviceAuthStore.storeToken( | |
| deviceId: deviceID, | |
| role: "operator", | |
| token: "operator", | |
| gatewayID: "gateway-a") | |
| DeviceAuthStore.clearToken(deviceId: deviceID, role: "node", gatewayID: "gateway-a") | |
| #expect(DeviceAuthStore.loadToken(deviceId: deviceID, role: "node")?.token == "unscoped") | |
| #expect(DeviceAuthStore.loadToken( | |
| deviceId: deviceID, | |
| role: "node", | |
| gatewayID: "gateway-a") == nil) | |
| #expect(DeviceAuthStore.loadToken( | |
| deviceId: deviceID, | |
| role: "node", | |
| gatewayID: "gateway-b")?.token == "gateway-b") | |
| DeviceAuthStore.clearToken(deviceId: deviceID, role: "node") | |
| #expect(DeviceAuthStore.loadToken(deviceId: deviceID, role: "node") == nil) | |
| #expect(DeviceAuthStore.loadToken( | |
| deviceId: deviceID, | |
| role: "node", | |
| gatewayID: "gateway-b") == nil) | |
| #expect(DeviceAuthStore.loadToken( | |
| deviceId: deviceID, | |
| role: "operator", | |
| gatewayID: "gateway-a")?.token == "operator") | |
| } | |
| (.stateDirectoryIsolated) | |
| func `retiring Gateway authority clears its roles without touching sibling devices or Gateways`() { | |
| let rows: [(device: String, gateway: String?, role: String)] = [ | |
| ("selected-device", "selected-gateway", "operator"), | |
| ("selected-device", "selected-gateway", "node"), | |
| ("other-device", "selected-gateway", "operator"), | |
| ("selected-device", "other-gateway", "operator"), | |
| ("selected-device", nil, "operator"), | |
| ] | |
| for row in rows { | |
| #expect(DeviceAuthStore.storeTokenPersisted( | |
| deviceId: row.device, role: row.role, token: "synthetic", gatewayID: row.gateway)) | |
| } | |
| #expect(DeviceAuthStore.clearGatewayTokensPersisted( | |
| deviceId: "selected-device", gatewayID: "selected-gateway")) | |
| for row in rows { | |
| let removed = row.device == "selected-device" && row.gateway == "selected-gateway" | |
| #expect((DeviceAuthStore.loadToken( | |
| deviceId: row.device, role: row.role, gatewayID: row.gateway) == nil) == removed) | |
| } | |
| } | |
| (.stateDirectoryIsolated) | |
| func `clear all removes only the selected profile identity rows`() { | |
| let primary = DeviceIdentityStore.loadOrCreate(profile: .primary) | |
| let node = DeviceIdentityStore.loadOrCreate(profile: .node) | |
| let share = DeviceIdentityStore.loadOrCreate(profile: .shareExtension) | |
| _ = DeviceAuthStore.storeToken(deviceId: primary.deviceId, role: "node", token: "primary") | |
| _ = DeviceAuthStore.storeToken( | |
| deviceId: node.deviceId, | |
| role: "node", | |
| token: "node", | |
| profile: .node) | |
| _ = DeviceAuthStore.storeToken( | |
| deviceId: share.deviceId, | |
| role: "node", | |
| token: "share", | |
| profile: .shareExtension) | |
| DeviceAuthStore.clearAll(profile: .shareExtension) | |
| #expect(DeviceAuthStore.loadToken(deviceId: primary.deviceId, role: "node")?.token == "primary") | |
| #expect(DeviceAuthStore.loadToken( | |
| deviceId: node.deviceId, | |
| role: "node", | |
| profile: .node)?.token == "node") | |
| #expect(DeviceAuthStore.loadToken( | |
| deviceId: share.deviceId, | |
| role: "node", | |
| profile: .shareExtension) == nil) | |
| } | |
| (.stateDirectoryIsolated) | |
| func `clear all without an identity leaves legacy state untouched`() throws { | |
| let legacy = DeviceAuthStoreFile( | |
| version: 1, | |
| deviceId: "orphaned-device", | |
| tokens: [ | |
| "node": DeviceAuthEntry( | |
| token: "legacy-token", | |
| role: "node", | |
| scopes: [], | |
| updatedAtMs: 100), | |
| ]) | |
| try Self.writeLegacy(legacy) | |
| let authURL = try Self.authURL() | |
| let original = try Data(contentsOf: authURL) | |
| DeviceAuthStore.clearAll() | |
| #expect(try Data(contentsOf: authURL) == original) | |
| #expect(try !FileManager.default.fileExists(atPath: Self.databaseURL().path)) | |
| } | |
| (.stateDirectoryIsolated) | |
| func `version zero bootstrap creates the exact composite key table`() throws { | |
| #expect(DeviceAuthStore.storeTokenPersisted( | |
| deviceId: "bootstrap-device", | |
| role: "node", | |
| token: "bootstrap-token")) | |
| let databaseURL = try Self.databaseURL() | |
| #expect(try Self.scalarInt(databaseURL, "PRAGMA user_version") == 0) | |
| #expect(try Self.scalarText( | |
| databaseURL, | |
| """ | |
| SELECT group_concat(name || ':' || pk, ',') | |
| FROM (SELECT name, pk FROM pragma_table_xinfo('device_auth_tokens') WHERE pk > 0 ORDER BY cid) | |
| """) == "device_id:1,role:2") | |
| #expect(try Self.scalarText( | |
| databaseURL, | |
| """ | |
| SELECT group_concat(name || ':' || "desc", ',') | |
| FROM ( | |
| SELECT name, "desc" FROM pragma_index_xinfo('idx_device_auth_tokens_updated') | |
| WHERE key = 1 ORDER BY seqno | |
| ) | |
| """) == "updated_at_ms:1,device_id:0,role:0") | |
| } | |
| (.stateDirectoryIsolated) | |
| func `versioned database never synthesizes a missing auth table`() throws { | |
| let databaseURL = try Self.databaseURL() | |
| try Self.execute(databaseURL, """ | |
| CREATE TABLE schema_meta ( | |
| meta_key TEXT NOT NULL PRIMARY KEY, | |
| role TEXT NOT NULL, | |
| schema_version INTEGER NOT NULL | |
| ) STRICT; | |
| INSERT INTO schema_meta (meta_key, role, schema_version) VALUES ('primary', 'global', 6); | |
| PRAGMA user_version = 6; | |
| """) | |
| #expect(!DeviceAuthStore.storeTokenPersisted( | |
| deviceId: "versioned-device", | |
| role: "node", | |
| token: "must-not-persist")) | |
| #expect(!DeviceAuthStore.clearGatewayTokensPersisted( | |
| deviceId: "versioned-device", gatewayID: "selected-gateway")) | |
| #expect(try Self.scalarInt( | |
| databaseURL, | |
| "SELECT COUNT(*) FROM sqlite_schema WHERE type = 'table' AND name = 'device_auth_tokens'") == 0) | |
| } | |
| } | |
| extension DeviceAuthStoreTests { | |
| private static func stateDirectoryURL() throws -> URL { | |
| let path = try #require(getenv("OPENCLAW_STATE_DIR").map { String(cString: $0) }) | |
| return URL(fileURLWithPath: path, isDirectory: true) | |
| } | |
| private static func databaseURL() throws -> URL { | |
| try self.stateDirectoryURL() | |
| .appendingPathComponent("state", isDirectory: true) | |
| .appendingPathComponent("openclaw.sqlite", isDirectory: false) | |
| } | |
| private static func authURL( | |
| profile: GatewayDeviceIdentityProfile = .primary) throws -> URL | |
| { | |
| try self.stateDirectoryURL() | |
| .appendingPathComponent("identity", isDirectory: true) | |
| .appendingPathComponent(profile.authFileName, isDirectory: false) | |
| } | |
| private static func writeLegacy( | |
| _ store: DeviceAuthStoreFile, | |
| profile: GatewayDeviceIdentityProfile = .primary) throws | |
| { | |
| let url = try self.authURL(profile: profile) | |
| try FileManager.default.createDirectory( | |
| at: url.deletingLastPathComponent(), | |
| withIntermediateDirectories: true) | |
| try JSONEncoder().encode(store).write(to: url, options: [.atomic]) | |
| } | |
| private static func storageComponent(_ value: String) -> String { | |
| Data(value.utf8).base64EncodedString() | |
| .replacingOccurrences(of: "+", with: "-") | |
| .replacingOccurrences(of: "/", with: "_") | |
| .replacingOccurrences(of: "=", with: "") | |
| } | |
| private static func execute(_ databaseURL: URL, _ sql: String) throws { | |
| try FileManager.default.createDirectory( | |
| at: databaseURL.deletingLastPathComponent(), | |
| withIntermediateDirectories: true) | |
| var database: OpaquePointer? | |
| guard sqlite3_open(databaseURL.path, &database) == SQLITE_OK, let database else { | |
| throw DeviceIdentityStore.storageError("Could not open test database") | |
| } | |
| defer { sqlite3_close(database) } | |
| guard sqlite3_exec(database, sql, nil, nil, nil) == SQLITE_OK else { | |
| throw DeviceIdentityStore.storageError(String(cString: sqlite3_errmsg(database))) | |
| } | |
| } | |
| private static func scalarInt(_ databaseURL: URL, _ sql: String) throws -> Int64 { | |
| try self.scalar(databaseURL, sql) { sqlite3_column_int64($0, 0) } | |
| } | |
| private static func scalarText(_ databaseURL: URL, _ sql: String) throws -> String? { | |
| try self.scalar(databaseURL, sql) { statement in | |
| sqlite3_column_text(statement, 0).map { String(cString: $0) } | |
| } | |
| } | |
| private static func scalar<T>( | |
| _ databaseURL: URL, | |
| _ sql: String, | |
| transform: (OpaquePointer) -> T) throws -> T | |
| { | |
| var database: OpaquePointer? | |
| guard sqlite3_open(databaseURL.path, &database) == SQLITE_OK, let database else { | |
| throw DeviceIdentityStore.storageError("Could not open test database") | |
| } | |
| defer { sqlite3_close(database) } | |
| var statement: OpaquePointer? | |
| guard sqlite3_prepare_v2(database, sql, -1, &statement, nil) == SQLITE_OK, let statement else { | |
| throw DeviceIdentityStore.storageError(String(cString: sqlite3_errmsg(database))) | |
| } | |
| defer { sqlite3_finalize(statement) } | |
| guard sqlite3_step(statement) == SQLITE_ROW else { | |
| throw DeviceIdentityStore.storageError(String(cString: sqlite3_errmsg(database))) | |
| } | |
| return transform(statement) | |
| } | |
| } | |