Skip to content

Commit eaa2bd6

Browse files
authored
Don't treat a missing passphrase as a validation error (#648)
1 parent d612029 commit eaa2bd6

File tree

6 files changed

+31
-25
lines changed

6 files changed

+31
-25
lines changed

CHANGELOG.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ All notable changes to this project will be documented in this file. Take a look
88

99
#### LCP
1010

11-
* The LCP License Document is now accessible via `publication.lcpLicense?.license`, even if the license validation fails with a status error. This is useful for checking the end date of an expired license, for example.
11+
* The LCP License Document is now accessible via `publication.lcpLicense?.license`, even if the license validation fails with a status error or missing passphrase. This is useful for checking the end date of an expired license or renew a license.
1212

1313

1414
## [3.4.0]

Sources/LCP/Content Protection/LCPContentProtection.swift

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -156,7 +156,7 @@ private final class LCPContentProtectionService: ContentProtectionService {
156156

157157
init(license: LCPLicense? = nil, error: Error? = nil) {
158158
self.license = license
159-
self.error = error ?? license?.error.map { LCPError.licenseStatus($0) }
159+
self.error = error ?? license?.error
160160
}
161161

162162
convenience init(result: Result<LCPLicense, LCPError>) {

Sources/LCP/LCPLicense.swift

Lines changed: 4 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -17,9 +17,10 @@ public protocol LCPLicense: UserRights {
1717
/// The license is restricted if there is a status error.
1818
var isRestricted: Bool { get }
1919

20-
/// Status error detected while validating the license, e.g. if the license
21-
/// is expired or revoked.
22-
var error: StatusError? { get }
20+
/// Error detected after validating the license, which prevents the user
21+
/// from decrypting the book. For example, the license is expired/revoked
22+
/// or the passphrase was not provided.
23+
var error: LCPError? { get }
2324

2425
/// Deciphers the given encrypted data to be displayed in the reader.
2526
func decipher(_ data: Data) throws -> Data?

Sources/LCP/License/License.swift

Lines changed: 11 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,7 @@ final class License: Loggable {
2828
self.httpClient = httpClient
2929

3030
validation.observe { [weak self] result in
31-
if case let .success(documents) = result, let documents = documents {
31+
if case let .success(documents) = result {
3232
self?.documents = documents
3333
}
3434
}
@@ -46,15 +46,22 @@ extension License: LCPLicense {
4646
}
4747

4848
public var isRestricted: Bool {
49-
error != nil
49+
documents.context.getOrNil() == nil
5050
}
5151

52-
public var error: StatusError? {
52+
public var error: LCPError? {
5353
switch documents.context {
5454
case .success:
5555
return nil
5656
case let .failure(error):
57-
return error
57+
switch error {
58+
// We don't report the missingPassphrase case as an error
59+
// because in this case the user cancelled the passphrase prompt.
60+
case .missingPassphrase:
61+
return nil
62+
default:
63+
return error
64+
}
5865
}
5966
}
6067

Sources/LCP/License/LicenseValidation.swift

Lines changed: 13 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -23,7 +23,7 @@ private let supportedProfiles = [
2323
"http://readium.org/lcp/profile-2.9",
2424
]
2525

26-
typealias Context = Result<LCPClientContext, StatusError>
26+
typealias Context = Result<LCPClientContext, LCPError>
2727

2828
// Holds the License/Status Documents and the DRM context, once validated.
2929
struct ValidatedDocuments {
@@ -98,7 +98,7 @@ final actor LicenseValidation: Loggable {
9898

9999
/// Validates the given License or Status Document.
100100
/// If a validation is already running, `LCPError.licenseIsBusy` will be reported.
101-
func validate(_ document: Document) async throws -> ValidatedDocuments? {
101+
func validate(_ document: Document) async throws -> ValidatedDocuments {
102102
let event: Event
103103
switch document {
104104
case let .license(data):
@@ -186,7 +186,7 @@ extension LicenseValidation {
186186
// 4. Check the dates and license status
187187
case let (.checkLicenseStatus(license, status, _), .checkedLicenseStatus(error)):
188188
if let error = error {
189-
self = .valid(ValidatedDocuments(license, .failure(error), status))
189+
self = .valid(ValidatedDocuments(license, .failure(.licenseStatus(error)), status))
190190
} else {
191191
self = .requestPassphrase(license, status)
192192
}
@@ -196,8 +196,8 @@ extension LicenseValidation {
196196
self = .validateIntegrity(license, status, passphrase: passphrase)
197197
case let (.requestPassphrase, .failed(error)):
198198
self = .failure(error)
199-
case (.requestPassphrase, .cancelled):
200-
self = .start
199+
case let (.requestPassphrase(license, status), .passphraseNotFound):
200+
self = .valid(ValidatedDocuments(license, .failure(.missingPassphrase), status))
201201

202202
// 6. Validate the license integrity
203203
case let (.validateIntegrity(license, status, _), .validatedIntegrity(context)):
@@ -254,8 +254,8 @@ extension LicenseValidation {
254254
case registeredDevice(Data?)
255255
// Raised when any error occurs during the validation workflow.
256256
case failed(Error)
257-
// Raised when the user cancelled the validation.
258-
case cancelled
257+
// Raised when no passphrase could be found or given by the user.
258+
case passphraseNotFound
259259
}
260260

261261
/// Should be called by the state handlers once they're done, to go to the next State.
@@ -358,7 +358,7 @@ extension LicenseValidation {
358358
) {
359359
try await raise(.retrievedPassphrase(passphrase))
360360
} else {
361-
try await raise(.cancelled)
361+
try await raise(.passphraseNotFound)
362362
}
363363
}
364364

@@ -386,8 +386,8 @@ extension LicenseValidation {
386386
do {
387387
switch state {
388388
case .start:
389-
// We are back to start? It means the validation was cancelled by the user.
390-
notifyObservers(.success(nil))
389+
// We are back to start? This should not happen.
390+
throw LCPError.runtime("Unexpected license validation state")
391391
case let .validateLicense(data, _):
392392
try await validateLicense(data: data)
393393
case let .fetchStatus(license):
@@ -417,7 +417,7 @@ extension LicenseValidation {
417417

418418
/// Validation observers
419419
extension LicenseValidation {
420-
typealias Observer = (Result<ValidatedDocuments?, Error>) -> Void
420+
typealias Observer = (Result<ValidatedDocuments, Error>) -> Void
421421

422422
enum ObserverPolicy {
423423
// The observer is automatically removed when called.
@@ -450,15 +450,15 @@ extension LicenseValidation {
450450
observers.append((observer, policy))
451451
}
452452

453-
func observe() async throws -> ValidatedDocuments? {
453+
func observe() async throws -> ValidatedDocuments {
454454
try await withCheckedThrowingContinuation { continuation in
455455
observe(.once) { result in
456456
continuation.resume(with: result)
457457
}
458458
}
459459
}
460460

461-
private func notifyObservers(_ result: Result<ValidatedDocuments?, Error>) {
461+
private func notifyObservers(_ result: Result<ValidatedDocuments, Error>) {
462462
for observer in observers {
463463
observer.callback(result)
464464
}

Sources/LCP/Services/LicensesService.swift

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -98,9 +98,7 @@ final class LicensesService: Loggable {
9898
onLicenseValidated: onLicenseValidated
9999
)
100100

101-
guard let documents = try await validation.validate(.license(initialData)) else {
102-
throw LCPError.missingPassphrase
103-
}
101+
let documents = try await validation.validate(.license(initialData))
104102

105103
return License(documents: documents, client: client, validation: validation, licenses: licenses, device: device, httpClient: httpClient)
106104
}

0 commit comments

Comments
 (0)