generated from bitwarden/template
-
Notifications
You must be signed in to change notification settings - Fork 37
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
BIT-100: Adds the known device API call (#18)
Co-authored-by: Matt Czech <[email protected]>
- Loading branch information
1 parent
3427490
commit 9426821
Showing
12 changed files
with
328 additions
and
5 deletions.
There are no files selected for viewing
28 changes: 28 additions & 0 deletions
28
BitwardenShared/Core/Auth/Models/Response/KnownDevice/KnownDeviceResponseModel.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
import Foundation | ||
import Networking | ||
|
||
// MARK: - KnownDeviceResponse | ||
|
||
/// An object containing a value defining if this device has previously logged into this account or not. | ||
struct KnownDeviceResponseModel: JSONResponse { | ||
static var decoder = JSONDecoder() | ||
|
||
// MARK: Properties | ||
|
||
/// A flag indicating if this device is known or not. | ||
var isKnownDevice: Bool | ||
|
||
// MARK: Initialization | ||
|
||
/// Creates a new `KnownDeviceResponseModel` instance. | ||
/// | ||
/// - Parameter isKnownDevice: A flag indicating if this device is known or not. | ||
init(isKnownDevice: Bool) { | ||
self.isKnownDevice = isKnownDevice | ||
} | ||
|
||
init(from decoder: Decoder) throws { | ||
let container = try decoder.singleValueContainer() | ||
isKnownDevice = try container.decode(Bool.self) | ||
} | ||
} |
33 changes: 33 additions & 0 deletions
33
BitwardenShared/Core/Auth/Models/Response/KnownDevice/KnownDeviceResponseModelTests.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,33 @@ | ||
import XCTest | ||
|
||
@testable import BitwardenShared | ||
|
||
// MARK: - KnownDeviceResponseModelTests | ||
|
||
class KnownDeviceResponseModelTests: BitwardenTestCase { | ||
// MARK: Init | ||
|
||
/// `init(isKnownDevice:)` sets the corresponding values. | ||
func test_init() { | ||
let subject = KnownDeviceResponseModel(isKnownDevice: true) | ||
XCTAssertTrue(subject.isKnownDevice) | ||
} | ||
|
||
// MARK: Decoding | ||
|
||
/// Validates decoding the `KnownDeviceFalse.json` fixture. | ||
func test_decode_False() throws { | ||
let json = APITestData.knownDeviceFalse.data | ||
let decoder = JSONDecoder() | ||
let subject = try decoder.decode(KnownDeviceResponseModel.self, from: json) | ||
XCTAssertFalse(subject.isKnownDevice) | ||
} | ||
|
||
/// Validates decoding the `KnownDeviceTrue.json` fixture. | ||
func test_decode_True() throws { | ||
let json = APITestData.knownDeviceTrue.data | ||
let decoder = JSONDecoder() | ||
let subject = try decoder.decode(KnownDeviceResponseModel.self, from: json) | ||
XCTAssertTrue(subject.isKnownDevice) | ||
} | ||
} |
25 changes: 25 additions & 0 deletions
25
BitwardenShared/Core/Auth/Services/API/Device/DeviceAPIService.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,25 @@ | ||
// MARK: - DeviceAPIService | ||
|
||
/// A protocol for an API service used to make device requests. | ||
/// | ||
protocol DeviceAPIService { | ||
/// Queries the API to determine if this device was previously associated with the email address. | ||
/// | ||
/// - Parameters: | ||
/// - email: The email being used to log into the app. | ||
/// - deviceIdentifier: The unique identifier for this device. | ||
/// | ||
/// - Returns: `true` if this email has been associated with this device, `false` otherwise. | ||
/// | ||
func knownDevice(email: String, deviceIdentifier: String) async throws -> Bool | ||
} | ||
|
||
// MARK: - APIService | ||
|
||
extension APIService: DeviceAPIService { | ||
func knownDevice(email: String, deviceIdentifier: String) async throws -> Bool { | ||
let request = KnownDeviceRequest(email: email, deviceIdentifier: deviceIdentifier) | ||
let response = try await apiService.send(request) | ||
return response.isKnownDevice | ||
} | ||
} |
73 changes: 73 additions & 0 deletions
73
BitwardenShared/Core/Auth/Services/API/Device/DeviceAPIServiceTests.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,73 @@ | ||
import XCTest | ||
|
||
@testable import BitwardenShared | ||
|
||
// MARK: - DeviceAPIServiceTests | ||
|
||
class DeviceAPIServiceTests: BitwardenTestCase { | ||
// MARK: Properties | ||
|
||
var client: MockHTTPClient! | ||
var subject: APIService! | ||
|
||
// MARK: Setup & Teardown | ||
|
||
override func setUp() { | ||
super.setUp() | ||
client = MockHTTPClient() | ||
subject = APIService(client: client) | ||
} | ||
|
||
override func tearDown() { | ||
super.tearDown() | ||
client = nil | ||
subject = nil | ||
} | ||
|
||
// MARK: Tests | ||
|
||
/// `knownDevice(email:deviceIdentifier:)` returns the correct value from the API with a successful request. | ||
func test_knownDevice_success() async throws { | ||
let resultData = APITestData.knownDeviceTrue | ||
client.result = .httpSuccess(testData: resultData) | ||
|
||
let isKnownDevice = try await subject.knownDevice( | ||
email: "[email protected]", | ||
deviceIdentifier: "1234" | ||
) | ||
|
||
let request = try XCTUnwrap(client.requests.first) | ||
XCTAssertEqual(request.method, .get) | ||
XCTAssertEqual(request.url.relativePath, "/api/devices/knowndevice") | ||
XCTAssertNil(request.body) | ||
XCTAssertEqual(request.headers["X-Request-Email"], "ZW1haWxAZXhhbXBsZS5jb20") | ||
XCTAssertEqual(request.headers["X-Device-Identifier"], "1234") | ||
|
||
XCTAssertTrue(isKnownDevice) | ||
} | ||
|
||
/// `knownDevice(email:deviceIdentifier:)` throws a decoding error if the response is not the expected type. | ||
func test_knownDevice_decodingFailure() async throws { | ||
let resultData = APITestData(data: Data("this should fail".utf8)) | ||
client.result = .httpSuccess(testData: resultData) | ||
|
||
await assertAsyncThrows { | ||
_ = try await subject.knownDevice( | ||
email: "[email protected]", | ||
deviceIdentifier: "1234" | ||
) | ||
} | ||
} | ||
|
||
/// `knownDevice(email:deviceIdentifier:)` throws an error if the request fails. | ||
func test_knownDevice_httpFailure() async { | ||
client.result = .httpFailure() | ||
|
||
await assertAsyncThrows { | ||
_ = try await subject.knownDevice( | ||
email: "[email protected]", | ||
deviceIdentifier: "1234" | ||
) | ||
} | ||
} | ||
} |
6 changes: 6 additions & 0 deletions
6
...enShared/Core/Auth/Services/API/Device/KnownDevice/Fixtures/APITestData+KnownDevice.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,6 @@ | ||
import Foundation | ||
|
||
extension APITestData { | ||
static let knownDeviceTrue = APITestData(data: Data("true".utf8)) | ||
static let knownDeviceFalse = APITestData(data: Data("false".utf8)) | ||
} |
28 changes: 28 additions & 0 deletions
28
BitwardenShared/Core/Auth/Services/API/Device/KnownDevice/KnownDeviceRequest.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,28 @@ | ||
import Foundation | ||
import Networking | ||
|
||
// MARK: - KnownDeviceRequest | ||
|
||
/// A request for determining if this is a known device. | ||
struct KnownDeviceRequest: Request { | ||
typealias Response = KnownDeviceResponseModel | ||
|
||
let path = "/devices/knowndevice" | ||
|
||
let headers: [String: String] | ||
|
||
/// Creates a new `KnownDeviceRequest` instance. | ||
/// | ||
/// - Parameters: | ||
/// - email: The email address for the user. | ||
/// - deviceIdentifier: The unique identifier for this device. | ||
/// | ||
init(email: String, deviceIdentifier: String) { | ||
let emailData = Data(email.utf8) | ||
let emailEncoded = emailData.base64EncodedString().urlEncoded() | ||
headers = [ | ||
"X-Request-Email": emailEncoded, | ||
"X-Device-Identifier": deviceIdentifier, | ||
] | ||
} | ||
} |
47 changes: 47 additions & 0 deletions
47
BitwardenShared/Core/Auth/Services/API/Device/KnownDevice/KnownDeviceRequestTests.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,47 @@ | ||
import XCTest | ||
|
||
@testable import BitwardenShared | ||
|
||
// MARK: - KnownDeviceRequestTests | ||
|
||
class KnownDeviceRequestTests: BitwardenTestCase { | ||
// MARK: Static Values | ||
|
||
/// `body` is `nil`. | ||
func test_body() { | ||
let subject = KnownDeviceRequest(email: "", deviceIdentifier: "") | ||
XCTAssertNil(subject.body) | ||
} | ||
|
||
/// `method` is `.get`. | ||
func test_method() { | ||
let subject = KnownDeviceRequest(email: "", deviceIdentifier: "") | ||
XCTAssertEqual(subject.method, .get) | ||
} | ||
|
||
/// `path` is the correct value. | ||
func test_path() { | ||
let subject = KnownDeviceRequest(email: "", deviceIdentifier: "") | ||
XCTAssertEqual(subject.path, "/devices/knowndevice") | ||
} | ||
|
||
/// `query` is empty. | ||
func test_query() { | ||
let subject = KnownDeviceRequest(email: "", deviceIdentifier: "") | ||
XCTAssertTrue(subject.query.isEmpty) | ||
} | ||
|
||
// MARK: Init | ||
|
||
/// `init()` encodes the provided values in to the request headers correctly. | ||
func test_init() { | ||
let subject = KnownDeviceRequest( | ||
email: "[email protected]", | ||
deviceIdentifier: "1234" | ||
) | ||
|
||
XCTAssertEqual(subject.headers.count, 2) | ||
XCTAssertEqual(subject.headers["X-Request-Email"], "ZW1haWxAZXhhbXBsZS5jb20") | ||
XCTAssertEqual(subject.headers["X-Device-Identifier"], "1234") | ||
} | ||
} |
5 changes: 0 additions & 5 deletions
5
BitwardenShared/Core/Auth/Services/API/DeviceAPIService.swift
This file was deleted.
Oops, something went wrong.
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
51 changes: 51 additions & 0 deletions
51
BitwardenShared/UI/Platform/Application/Extensions/String.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,51 @@ | ||
import Foundation | ||
|
||
// MARK: - URLDecodingError | ||
|
||
/// Errors that can be encountered when attempting to decode a string from it's url encoded format. | ||
enum URLDecodingError: Error, Equatable { | ||
/// The provided string is an invalid length. | ||
/// | ||
/// Base64 encoded strings are padded at the end with `=` characters to ensure that the length of the resulting | ||
/// value is divisible by `4`. However, Base64 encoded strings _cannot_ have a remainder of `1` when divided by | ||
/// `4`. | ||
/// | ||
/// Example: `YMFhY` is considered invalid, and attempting to decode this value from a url or header value will | ||
/// throw this error. | ||
/// | ||
case invalidLength | ||
} | ||
|
||
// MARK: - String | ||
|
||
extension String { | ||
// MARK: Methods | ||
|
||
/// Creates a new string that has been encoded for use in a url or request header. | ||
/// | ||
/// - Returns: A `String` encoded for use in a url or request header. | ||
/// | ||
func urlEncoded() -> String { | ||
replacingOccurrences(of: "+", with: "-") | ||
.replacingOccurrences(of: "/", with: "_") | ||
.replacingOccurrences(of: "=", with: "") | ||
} | ||
|
||
/// Creates a new string that has been decoded from a url or request header. | ||
/// | ||
/// - Throws: `URLDecodingError.invalidLength` if the length of this string is invalid. | ||
/// | ||
/// - Returns: A `String` decoded from use in a url or request header. | ||
/// | ||
func urlDecoded() throws -> String { | ||
let remainder = count % 4 | ||
guard remainder != 1 else { throw URLDecodingError.invalidLength } | ||
|
||
return replacingOccurrences(of: "-", with: "+") | ||
.replacingOccurrences(of: "_", with: "/") | ||
.appending(String( | ||
repeating: "=", | ||
count: remainder == 0 ? 0 : 4 - remainder | ||
)) | ||
} | ||
} |
31 changes: 31 additions & 0 deletions
31
BitwardenShared/UI/Platform/Application/Extensions/StringTests.swift
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,31 @@ | ||
import XCTest | ||
|
||
@testable import BitwardenShared | ||
|
||
// MARK: - StringTests | ||
|
||
class StringTests: BitwardenTestCase { | ||
// MARK: Tests | ||
|
||
func test_urlDecoded_withInvalidString() { | ||
let subject = "a_bc-" | ||
|
||
XCTAssertThrowsError(try subject.urlDecoded()) { error in | ||
XCTAssertEqual(error as? URLDecodingError, .invalidLength) | ||
} | ||
} | ||
|
||
func test_urlDecoded_withValidString() throws { | ||
let subject = "a_bcd-" | ||
let decoded = try subject.urlDecoded() | ||
|
||
XCTAssertEqual(decoded, "a/bcd+==") | ||
} | ||
|
||
func test_urlEncoded() { | ||
let subject = "a/bcd+==" | ||
let encoded = subject.urlEncoded() | ||
|
||
XCTAssertEqual(encoded, "a_bcd-") | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters