Skip to content

Commit

Permalink
Add list() to the storage manager. (#24)
Browse files Browse the repository at this point in the history
* Add list() to the storage manager.

This produces a list of keys for the items currently in secure storage.

---------

Co-authored-by: Todd Showalter <[email protected]>
  • Loading branch information
todd-spruceid and Todd Showalter authored Aug 15, 2024
1 parent e5f0ca5 commit 4181b29
Show file tree
Hide file tree
Showing 7 changed files with 157 additions and 145 deletions.
4 changes: 2 additions & 2 deletions Package.resolved
Original file line number Diff line number Diff line change
Expand Up @@ -5,8 +5,8 @@
"kind" : "remoteSourceControl",
"location" : "https://github.com/spruceid/mobile-sdk-rs.git",
"state" : {
"revision" : "e3ed692d6b9530601eefb30c8bab125c764ae291",
"version" : "0.0.26"
"revision" : "9deb085da5d26630045505cad6f46f11460d739d",
"version" : "0.0.27"
}
},
{
Expand Down
3 changes: 2 additions & 1 deletion Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@ let package = Package(
targets: ["SpruceIDMobileSdk"])
],
dependencies: [
.package(url: "https://github.com/spruceid/mobile-sdk-rs.git", from: "0.0.26"),
// .package(url: "https://github.com/spruceid/mobile-sdk-rs.git", .branch("main")),
.package(url: "https://github.com/spruceid/mobile-sdk-rs.git", from: "0.0.27"),
// .package(path: "../mobile-sdk-rs"),
.package(url: "https://github.com/apple/swift-algorithms", from: "1.2.0")
],
Expand Down
271 changes: 132 additions & 139 deletions Sources/MobileSdk/StorageManager.swift
Original file line number Diff line number Diff line change
@@ -1,145 +1,138 @@
// File: Storage Manager
//
// Store and retrieve sensitive data. Data is stored in the Application Support directory of the app, encrypted in
// place via the .completeFileProtection option, and marked as excluded from backups so it will not be included in
// iCloud backps.

//
// Imports
//
/// Storage Manager
///
/// Store and retrieve sensitive data. Data is stored in the Application Support directory of the app, encrypted in
/// place via the .completeFileProtection option, and marked as excluded from backups so it will not be included in
/// iCloud backps.

import Foundation

//
// Code
//

// Class: StorageManager
// Store and retrieve sensitive data.

class StorageManager: NSObject {
// Local-Method: path()
// Get the path to the application support dir, appending the given file name to it. We use the application
// support directory because its contents are not shared.
//
// Arguments:
// file - the name of the file
//
// Returns:
// An URL for the named file in the app's Application Support directory.

private func path(file: String) -> URL? {
do {
// Get the applications support dir, and tack the name of the thing we're storing on the end of it.
// This does imply that `file` should be a valid filename.

let asdir = try FileManager.default.url(for: .applicationSupportDirectory,
in: .userDomainMask,
appropriateFor: nil, // Ignored
create: true) // May not exist, make if necessary.

return asdir.appendingPathComponent(file)
} catch { // Did the attempt to get the application support dir fail?
print("Failed to get/create the application support dir.")
return nil
}
}

// Method: add()
// Store a value for a specified key, encrypted in place.
//
// Arguments:
// key - the name of the file
// value - the data to store
//
// Returns:
// A boolean indicating success.

func add(key: String, value: Data) -> Bool {
guard let file = path(file: key) else { return false }

do {
try value.write(to: file, options: .completeFileProtection)
} catch {
print("Failed to write the data for '\(key)'.")
return false
}

return true
}

// Method: get()
// Get a value for the specified key.
//
// Arguments:
// key - the name associated with the data
//
// Returns:
// Optional data potentially containing the value associated with the key; may be `nil`.

func get(key: String) -> Data? {
guard let file = path(file: key) else { return nil }

do {
let data = try Data(contentsOf: file)
return data
} catch {
print("Failed to read '\(file)'.")
}

return nil
}

// Method: remove()
// Remove a key/value pair. Removing a nonexistent key/value pair is not an error.
//
// Arguments:
// key - the name of the file
//
// Returns:
// A boolean indicating success; at present, there is no failure path, but this may change in the future.

func remove(key: String) -> Bool {
guard let file = path(file: key) else { return true }

do {
try FileManager.default.removeItem(at: file)
} catch {
// It's fine if the file isn't there.
}

return true
}

// Method: sys_test()
// Check to see if everything works.

func sys_test() {
let key = "test_key"
let value = Data("Some random string of text. 😎".utf8)

if !add(key: key, value: value) {
print("\(self.classForCoder):\(#function): Failed add() key/value pair.")
return
}

guard let payload = get(key: key) else {
print("\(self.classForCoder):\(#function): Failed get() value for key.")
return
}

if !(payload == value) {
print("\(self.classForCoder):\(#function): Mismatch between stored & retrieved value.")
return
}

if !remove(key: key) {
print("\(self.classForCoder):\(#function): Failed to delete key/value pair.")
return
}

print("\(self.classForCoder):\(#function): Completed successfully.")
}
import SpruceIDMobileSdkRs

// The following is a stripped-down version of the protocol definition from mobile-sdk-rs against which the storage
// manager is intended to link.

/// Store and retrieve sensitive data.
class StorageManager: NSObject, StorageManagerInterface {
/// Get the path to the application support dir, appending the given file name to it.
///
/// We use the application support directory because its contents are not shared.
///
/// - Parameters:
/// - file: the name of the file
///
/// - Returns: An URL for the named file in the app's Application Support directory.

private func path(file: String) -> URL? {
do {
// Get the applications support dir, and tack the name of the thing we're storing on the end of it.
// This does imply that `file` should be a valid filename.

let fileman = FileManager.default
let bundle = Bundle.main

let asdir = try fileman.url(for: .applicationSupportDirectory,
in: .userDomainMask,
appropriateFor: nil, // Ignored
create: true) // May not exist, make if necessary.

// If we create subdirectories in the application support directory, we need to put them in a subdir
// named after the app; normally, that's `CFBundleDisplayName` from `info.plist`, but that key doesn't
// have to be set, in which case we need to use `CFBundleName`.

guard let appname = bundle.object(forInfoDictionaryKey: "CFBundleDisplayName") as? String ??
bundle.object(forInfoDictionaryKey: "CFBundleName") as? String
else {
return nil
}

let datadir: URL

if #available(iOS 16.0, *) {
datadir = asdir.appending(path: "\(appname)/sprucekit/datastore/", directoryHint: .isDirectory)
} else {
datadir = asdir.appendingPathComponent("\(appname)/sprucekit/datastore/")
}

if !fileman.fileExists(atPath: datadir.path) {
try fileman.createDirectory(at: datadir, withIntermediateDirectories: true, attributes: nil)
}

return datadir.appendingPathComponent(file)
} catch {
return nil
}
}

/// Store a value for a specified key, encrypted in place.
///
/// - Parameters:
/// - key: the name of the file
/// - value: the data to store
///
/// - Returns: a boolean indicating success

func add(key: Key, value: Value) throws {
guard let file = path(file: key) else { throw StorageManagerError.InternalError }

do {
try value.write(to: file, options: .completeFileProtection)
} catch {
throw StorageManagerError.InternalError
}
}

/// Get a value for the specified key.
///
/// - Parameters:
/// - key: the name associated with the data
///
/// - Returns: optional data potentially containing the value associated with the key; may be `nil`

func get(key: Key) throws -> Value? {
guard let file = path(file: key) else { throw StorageManagerError.InternalError }

do {
return try Data(contentsOf: file)
} catch {
throw StorageManagerError.InternalError
}
}

/// List the the items in storage.
///
/// Note that this will list all items in the `application support` directory, potentially including any files
/// created by other systems.
///
/// - Returns: a list of items in storage

func list() throws -> [Key] {
guard let asdir = path(file: "")?.path else { return [String]() }

do {
return try FileManager.default.contentsOfDirectory(atPath: asdir)
} catch {
throw StorageManagerError.InternalError
}
}

/// Remove a key/value pair.
///
/// Removing a nonexistent key/value pair is not an error.
///
/// - Parameters:
/// - key: the name of the file
///
/// - Returns: a boolean indicating success; at present, there is no failure path, but this may change

func remove(key: Key) throws {
guard let file = path(file: key) else { return }

do {
try FileManager.default.removeItem(at: file)
} catch {
// It's fine if the file isn't there.
}
}
}

//
Expand Down
2 changes: 1 addition & 1 deletion Sources/MobileSdk/ui/QRCodeScanner.swift
Original file line number Diff line number Diff line change
Expand Up @@ -83,7 +83,7 @@ public struct QRCodeScanner: View {
@State private var qrOutput: AVCaptureMetadataOutput = .init()

/// Camera QR Output delegate
@StateObject private var qrDelegate = QRScannerDelegate()
@ObservedObject private var qrDelegate = QRScannerDelegate() // Was @StateObject, but that requires iOS 14.

/// Scanned code
@State private var scannedCode: String = ""
Expand Down
2 changes: 1 addition & 1 deletion SpruceIDMobileSdk.podspec
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ Pod::Spec.new do |spec|
spec.source_files = "Sources/MobileSdk/*.swift"

spec.static_framework = true
spec.dependency 'SpruceIDMobileSdkRs', "~> 0.0.26"
spec.dependency 'SpruceIDMobileSdkRs', "~> 0.0.27"
spec.dependency 'SwiftAlgorithms', "~> 1.0.0"
spec.frameworks = 'Foundation', 'CoreBluetooth', 'CryptoKit'
end
18 changes: 18 additions & 0 deletions Tests/MobileSdkTests/StorageManager.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
import XCTest
@testable import SpruceIDMobileSdk

final class StorageManagerTest: XCTestCase {
func testStorage() throws {
let storeman = StorageManager()
let key = "test_key"
let value = Data("Some random string of text. 😎".utf8)

XCTAssertNoThrow(try storeman.add(key: key, value: value))

let payload = try storeman.get(key: key)

XCTAssert(payload == value, "\(classForCoder):\(#function): Mismatch between stored & retrieved value.")

XCTAssertNoThrow(try storeman.remove(key: key))
}
}
2 changes: 1 addition & 1 deletion project.yml
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@ options:
packages:
SpruceIDMobileSdkRs:
url: https://github.com/spruceid/mobile-sdk-rs
from: 0.0.4
from: 0.0.27
# path: "../mobile-sdk-rs"
SwiftAlgorithms:
url: https://github.com/apple/swift-algorithms
Expand Down

0 comments on commit 4181b29

Please sign in to comment.