Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Decouple Credentials Manager storage from SimpleKeychain [SDK-2936] #551

Merged
merged 6 commits into from
Nov 19, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
44 changes: 38 additions & 6 deletions Auth0/CredentialsManager.swift
Original file line number Diff line number Diff line change
Expand Up @@ -5,10 +5,42 @@ import JWTDecode
import LocalAuthentication
#endif

/// Generic storage API for storing credentials
public protocol CredentialsStorage {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It's fine for it to be on the same file as the Credentials Manager

/// Retrieve a storage entry
///
/// - Parameters:
/// - forKey: The key to get from the store
/// - Returns: The stored data
func getEntry(forKey: String) -> Data?
/// Set a storage entry
///
/// - Parameters:
/// - _: The data to be stored
/// - forKey: The key to store it to
/// - Returns: if credentials were stored
func setEntry(_: Data, forKey: String) -> Bool
/// Delete a storage entry
///
/// - Parameters:
/// - forKey: The key to delete from the store
/// - Returns: if credentials were deleted
func deleteEntry(forKey: String) -> Bool
}

extension A0SimpleKeychain: CredentialsStorage {
public func getEntry(forKey: String) -> Data? {
return data(forKey: forKey)
}
public func setEntry(_ data: Data, forKey: String) -> Bool {
return setData(data, forKey: forKey)
}
}

/// Credentials management utility
public struct CredentialsManager {

private let storage: A0SimpleKeychain
private let storage: CredentialsStorage
private let storeKey: String
private let authentication: Authentication
private let dispatchQueue = DispatchQueue(label: "com.auth0.credentialsmanager.serial")
Expand All @@ -23,7 +55,7 @@ public struct CredentialsManager {
/// - authentication: Auth0 authentication instance
/// - storeKey: Key used to store user credentials in the keychain, defaults to "credentials"
/// - storage: The A0SimpleKeychain instance used to manage credentials storage. Defaults to a standard A0SimpleKeychain instance
public init(authentication: Authentication, storeKey: String = "credentials", storage: A0SimpleKeychain = A0SimpleKeychain()) {
public init(authentication: Authentication, storeKey: String = "credentials", storage: CredentialsStorage = A0SimpleKeychain()) {
self.storeKey = storeKey
self.authentication = authentication
self.storage = storage
Expand Down Expand Up @@ -63,7 +95,7 @@ public struct CredentialsManager {
guard let data = try? NSKeyedArchiver.archivedData(withRootObject: credentials, requiringSecureCoding: true) else {
return false
}
return self.storage.setData(data, forKey: storeKey)
return self.storage.setEntry(data, forKey: storeKey)
}

/// Clear credentials stored in keychain
Expand All @@ -81,7 +113,7 @@ public struct CredentialsManager {
/// - Parameter callback: callback with an error if the refresh token could not be revoked
public func revoke(_ callback: @escaping (CredentialsManagerError?) -> Void) {
guard
let data = self.storage.data(forKey: self.storeKey),
let data = self.storage.getEntry(forKey: self.storeKey),
let credentials = try? NSKeyedUnarchiver.unarchivedObject(ofClass: Credentials.self, from: data),
let refreshToken = credentials.refreshToken else {
_ = self.clear()
Expand All @@ -106,7 +138,7 @@ public struct CredentialsManager {
/// - Parameter minTTL: minimum lifetime in seconds the access token must have left.
/// - Returns: if there are valid and non-expired credentials stored.
public func hasValid(minTTL: Int = 0) -> Bool {
guard let data = self.storage.data(forKey: self.storeKey),
guard let data = self.storage.getEntry(forKey: self.storeKey),
let credentials = try? NSKeyedUnarchiver.unarchivedObject(ofClass: Credentials.self, from: data) else { return false }
return (!self.hasExpired(credentials) && !self.willExpire(credentials, within: minTTL)) || credentials.refreshToken != nil
}
Expand Down Expand Up @@ -150,7 +182,7 @@ public struct CredentialsManager {
#endif

private func retrieveCredentials() -> Credentials? {
guard let data = self.storage.data(forKey: self.storeKey),
guard let data = self.storage.getEntry(forKey: self.storeKey),
let credentials = try? NSKeyedUnarchiver.unarchivedObject(ofClass: Credentials.self, from: data) else { return nil }

return credentials
Expand Down
39 changes: 38 additions & 1 deletion Auth0Tests/CredentialsManagerSpec.swift
Original file line number Diff line number Diff line change
Expand Up @@ -59,7 +59,44 @@ class CredentialsManagerSpec: QuickSpec {
expect(credentialsManager.clear()).to(beFalse())
}
}


describe("custom storage") {

class CustomStore: CredentialsStorage {
var store: [String: Data] = [:]
func getEntry(forKey: String) -> Data? {
return store[forKey]
}
func setEntry(_ data: Data, forKey: String) -> Bool {
store[forKey] = data
return true
}
func deleteEntry(forKey: String) -> Bool {
store[forKey] = nil
return true
}
}

beforeEach {
credentialsManager = CredentialsManager(authentication: authentication, storage: CustomStore());
}

afterEach {
_ = credentialsManager.clear()
}

it("should store credentials in custom store") {
expect(credentialsManager.store(credentials: credentials)).to(beTrue())
expect(credentialsManager.hasValid()).to(beTrue())
}

it("should clear credentials from custom store") {
expect(credentialsManager.store(credentials: credentials)).to(beTrue())
expect(credentialsManager.clear()).to(beTrue())
expect(credentialsManager.hasValid()).to(beFalse())
}
}

describe("clearing and revoking refresh token") {

beforeEach {
Expand Down
27 changes: 26 additions & 1 deletion V2_MIGRATION_GUIDE.md
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ The following classes were also removed, as they are no longer in use:
- `Profile`
- `Identity`

## Metods Removed
## Methods Removed

The iOS-only method `resumeAuth(_:options:)` and the macOS-only method `resumeAuth(_:)` were removed from the library, as they are no longer needed.

Expand Down Expand Up @@ -202,6 +202,31 @@ In the following methods the `scope` parameter became non-optional (with a defau

The `multifactorChallenge(mfaToken:types:authenticatorId:)` method lost its `channel` parameter, which is no longer necessary.

### Credentials Manager

`CredentialsManager` now takes a `CredentialsStorage` protocol as it's storage argument rather than an instance of `SimpleKeychain`.

This means you can now provide your own storage layer to `CredentialsManager`.

```swift
class CustomStore: CredentialsStorage {
var store: [String : Data] = [:]
func getEntry(forKey: String) -> Data? {
return store[forKey]
}
func setEntry(_ data: Data, forKey: String) -> Bool {
store[forKey] = data
return true
}
func deleteEntry(forKey: String) -> Bool {
store[forKey] = nil
return true
}
}

let credentialsManager = CredentialsManager(authentication: authentication, storage: CustomStore());
```

## Behavior changes

### `openid` scope enforced on Web Auth
Expand Down