-
Notifications
You must be signed in to change notification settings - Fork 16
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
PIA-1845: Add Dedicated IP native endpoint
- Loading branch information
1 parent
0f0daed
commit 2b8ddeb
Showing
24 changed files
with
818 additions
and
112 deletions.
There are no files selected for viewing
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
46 changes: 46 additions & 0 deletions
46
Sources/PIALibrary/Account/Data/DedicatedIPServerMapper.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,46 @@ | ||
|
||
import Foundation | ||
|
||
class DedicatedIPServerMapper: DedicatedIPServerMapperType { | ||
private let dedicatedIPTokenHandler: DedicatedIPTokenHandlerType | ||
|
||
init(dedicatedIPTokenHandler: DedicatedIPTokenHandlerType) { | ||
self.dedicatedIPTokenHandler = dedicatedIPTokenHandler | ||
} | ||
|
||
func map(dedicatedIps: [DedicatedIPInformation]) -> Result<[Server], ClientError> { | ||
var dipRegions = [Server]() | ||
|
||
for dipServer in dedicatedIps { | ||
let status = DedicatedIPStatus(fromAPIStatus: dipServer.status) | ||
|
||
switch dipServer.status { | ||
case .active: | ||
|
||
guard let firstServer = Client.providers.serverProvider.currentServers.first(where: {$0.regionIdentifier == dipServer.id}) else { | ||
return .failure(ClientError.malformedResponseData) | ||
} | ||
|
||
guard let ip = dipServer.ip, let cn = dipServer.cn, let expirationTime = dipServer.dipExpire else { | ||
return .failure(ClientError.malformedResponseData) | ||
} | ||
|
||
let dipUsername = "dedicated_ip_"+dipServer.dipToken+"_"+String.random(length: 8) | ||
let expiringDate = Date(timeIntervalSince1970: TimeInterval(expirationTime)) | ||
let server = Server.ServerAddressIP(ip: ip, cn: cn, van: false) | ||
|
||
let dipRegion = Server(serial: firstServer.serial, name: firstServer.name, country: firstServer.country, hostname: firstServer.hostname, openVPNAddressesForTCP: [server], openVPNAddressesForUDP: [server], wireGuardAddressesForUDP: [server], iKEv2AddressesForUDP: [server], pingAddress: firstServer.pingAddress, geo: false, meta: nil, dipExpire: expiringDate, dipToken: dipServer.dipToken, dipStatus: status, dipUsername: dipUsername, regionIdentifier: firstServer.regionIdentifier) | ||
|
||
dipRegions.append(dipRegion) | ||
dedicatedIPTokenHandler(dedicatedIp: dipServer, dipUsername: dipUsername) | ||
|
||
default: | ||
|
||
let dipRegion = Server(serial: "", name: "", country: "", hostname: "", openVPNAddressesForTCP: [], openVPNAddressesForUDP: [], wireGuardAddressesForUDP: [], iKEv2AddressesForUDP: [], pingAddress: nil, geo: false, meta: nil, dipExpire: nil, dipToken: nil, dipStatus: status, dipUsername: nil, regionIdentifier: "") | ||
dipRegions.append(dipRegion) | ||
} | ||
} | ||
|
||
return .success(dipRegions) | ||
} | ||
} |
21 changes: 21 additions & 0 deletions
21
Sources/PIALibrary/Account/Data/DedicatedIPTokenHandler.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,21 @@ | ||
|
||
import Foundation | ||
|
||
class DedicatedIPTokenHandler: DedicatedIPTokenHandlerType { | ||
private let secureStore: SecureStore | ||
|
||
init(secureStore: SecureStore) { | ||
self.secureStore = secureStore | ||
} | ||
|
||
func callAsFunction(dedicatedIp: DedicatedIPInformation, dipUsername: String) { | ||
if dedicatedIp.isAboutToExpire { | ||
Macros.postNotification(.PIADIPRegionExpiring, [.token : dedicatedIp.dipToken]) | ||
} | ||
|
||
Macros.postNotification(.PIADIPCheckIP, [.token : dedicatedIp.dipToken, .ip : dedicatedIp.ip!]) | ||
|
||
secureStore.setDIPToken(dedicatedIp.dipToken) | ||
secureStore.setPassword(dedicatedIp.ip!, forDipToken: dipUsername) | ||
} | ||
} |
19 changes: 19 additions & 0 deletions
19
Sources/PIALibrary/Account/Data/Networking/GetDedicatedIPsRequestConfiguration.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,19 @@ | ||
|
||
import Foundation | ||
import NWHttpConnection | ||
|
||
struct GetDedicatedIPsRequestConfiguration: NetworkRequestConfigurationType { | ||
let networkRequestModule: NetworkRequestModule = .account | ||
let path: RequestAPI.Path = .dedicatedIp | ||
let httpMethod: NWHttpConnection.NWConnectionHTTPMethod = .post | ||
let contentType: NetworkRequestContentType = .json | ||
var inlcudeAuthHeaders: Bool = true | ||
var urlQueryParameters: [String : String]? = nil | ||
let responseDataType: NWDataResponseType = .jsonData | ||
|
||
var body: Data? = nil | ||
var otherHeaders: [String : String]? = nil | ||
|
||
let timeout: TimeInterval = 10 | ||
let requestQueue: DispatchQueue? = DispatchQueue(label: "getDedicatedIPs_request.queue") | ||
} |
19 changes: 19 additions & 0 deletions
19
Sources/PIALibrary/Account/Data/Networking/RenewDedicatedIPRequestConfiguration.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,19 @@ | ||
|
||
import Foundation | ||
import NWHttpConnection | ||
|
||
struct RenewDedicatedIPRequestConfiguration: NetworkRequestConfigurationType { | ||
let networkRequestModule: NetworkRequestModule = .account | ||
let path: RequestAPI.Path = .renewDedicatedIp | ||
let httpMethod: NWHttpConnection.NWConnectionHTTPMethod = .post | ||
let contentType: NetworkRequestContentType = .json | ||
var inlcudeAuthHeaders: Bool = true | ||
var urlQueryParameters: [String : String]? = nil | ||
let responseDataType: NWDataResponseType = .jsonData | ||
|
||
var body: Data? = nil | ||
var otherHeaders: [String : String]? = nil | ||
|
||
let timeout: TimeInterval = 10 | ||
let requestQueue: DispatchQueue? = DispatchQueue(label: "RenewDedicatedIP_request.queue") | ||
} |
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
46 changes: 46 additions & 0 deletions
46
Sources/PIALibrary/Account/Domain/Entities/DedicatedIPInformation.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,46 @@ | ||
|
||
import Foundation | ||
|
||
struct DedicatedIPInformationResult: Codable { | ||
let result: [DedicatedIPInformation] | ||
} | ||
|
||
public struct DedicatedIPInformation: Codable { | ||
enum Status: String, Codable { | ||
case active, expired, invalid, error | ||
} | ||
|
||
let id: String? | ||
let ip: String? | ||
let cn: String? | ||
let groups: [String]? | ||
let dipExpire: Double? | ||
let dipToken: String | ||
let status: DedicatedIPInformation.Status | ||
|
||
enum CodingKeys: String, CodingKey { | ||
case id = "id" | ||
case ip = "ip" | ||
case cn = "cn" | ||
case groups = "groups" | ||
case dipExpire = "dip_expire" | ||
case dipToken = "dip_token" | ||
case status = "status" | ||
} | ||
|
||
//Expiring in 5 days or less | ||
var isAboutToExpire: Bool { | ||
guard let dipExpire, let nextDays = Calendar.current.date(byAdding: .day, value: 5, to: Date()) | ||
else { | ||
return true | ||
} | ||
|
||
let expiringDate = Date(timeIntervalSince1970: TimeInterval(dipExpire)) | ||
return nextDays >= expiringDate | ||
} | ||
|
||
static func makeWith(data: Data) -> [DedicatedIPInformation]? { | ||
let dto = try? JSONDecoder().decode(DedicatedIPInformationResult.self, from: data) | ||
return dto?.result | ||
} | ||
} |
6 changes: 6 additions & 0 deletions
6
Sources/PIALibrary/Account/Domain/Interfaces/DedicatedIPServerMapperType.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 | ||
|
||
protocol DedicatedIPServerMapperType { | ||
func map(dedicatedIps: [DedicatedIPInformation]) -> Result<[Server], ClientError> | ||
} |
6 changes: 6 additions & 0 deletions
6
Sources/PIALibrary/Account/Domain/Interfaces/DedicatedIPTokenHandlerType.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 | ||
|
||
protocol DedicatedIPTokenHandlerType { | ||
func callAsFunction(dedicatedIp: DedicatedIPInformation, dipUsername: String) | ||
} |
75 changes: 75 additions & 0 deletions
75
Sources/PIALibrary/Account/Domain/UseCases/GetDedicatedIPsUseCase.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,75 @@ | ||
|
||
import Foundation | ||
|
||
public protocol GetDedicatedIPsUseCaseType { | ||
typealias Completion = ((Result<[DedicatedIPInformation], NetworkRequestError>) -> Void) | ||
func callAsFunction(dipTokens: [String], completion: @escaping Completion) | ||
} | ||
|
||
class GetDedicatedIPsUseCase: GetDedicatedIPsUseCaseType { | ||
private let networkClient: NetworkRequestClientType | ||
private let refreshAuthTokensChecker: RefreshAuthTokensCheckerType | ||
|
||
init(networkClient: NetworkRequestClientType, refreshAuthTokensChecker: RefreshAuthTokensCheckerType) { | ||
self.networkClient = networkClient | ||
self.refreshAuthTokensChecker = refreshAuthTokensChecker | ||
} | ||
|
||
func callAsFunction(dipTokens: [String], completion: @escaping Completion) { | ||
refreshAuthTokensChecker.refreshIfNeeded { [weak self] error in | ||
guard let self else { return } | ||
if let error { | ||
completion(.failure(error)) | ||
} else { | ||
networkClient.executeRequest(with: makeConfiguration(dipTokens: dipTokens)) { error, dataResponse in | ||
if let error { | ||
self.handleErrorResponse(error, completion: completion) | ||
} else if let dataResponse { | ||
self.handleDataResponse(dataResponse, completion: completion) | ||
} else { | ||
completion(.failure(NetworkRequestError.allConnectionAttemptsFailed())) | ||
} | ||
} | ||
} | ||
} | ||
} | ||
|
||
private func makeConfiguration(dipTokens: [String]) -> GetDedicatedIPsRequestConfiguration { | ||
var configuration = GetDedicatedIPsRequestConfiguration() | ||
|
||
let bodyDataDict = ["tokens": dipTokens] | ||
|
||
if let bodyData = try? JSONEncoder().encode(bodyDataDict) { | ||
configuration.body = bodyData | ||
} | ||
|
||
return configuration | ||
} | ||
|
||
private func handleErrorResponse(_ error: NetworkRequestError, completion: @escaping GetDedicatedIPsUseCaseType.Completion) { | ||
switch error { | ||
case .allConnectionAttemptsFailed(let statusCode): | ||
completion(.failure(statusCode == 401 ? NetworkRequestError.unauthorized : error)) | ||
return | ||
case .connectionError(statusCode: let statusCode, message: _): | ||
completion(.failure(statusCode == 401 ? NetworkRequestError.unauthorized : error)) | ||
return | ||
default: | ||
completion(.failure(error)) | ||
} | ||
} | ||
|
||
private func handleDataResponse(_ dataResponse: NetworkRequestResponseType, completion: @escaping GetDedicatedIPsUseCaseType.Completion) { | ||
guard let dataResponseContent = dataResponse.data else { | ||
completion(.failure(NetworkRequestError.noDataContent)) | ||
return | ||
} | ||
|
||
guard let dto = DedicatedIPInformation.makeWith(data: dataResponseContent) else { | ||
completion(.failure(NetworkRequestError.unableToDecodeDataContent)) | ||
return | ||
} | ||
|
||
completion(.success(dto)) | ||
} | ||
} |
60 changes: 60 additions & 0 deletions
60
Sources/PIALibrary/Account/Domain/UseCases/RenewDedicatedIPUseCase.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,60 @@ | ||
|
||
import Foundation | ||
|
||
public protocol RenewDedicatedIPUseCaseType { | ||
typealias Completion = ((Result<Void, NetworkRequestError>) -> Void) | ||
func callAsFunction(dipToken: String, completion: @escaping Completion) | ||
} | ||
|
||
class RenewDedicatedIPUseCase: RenewDedicatedIPUseCaseType { | ||
private let networkClient: NetworkRequestClientType | ||
private let refreshAuthTokensChecker: RefreshAuthTokensCheckerType | ||
|
||
init(networkClient: NetworkRequestClientType, refreshAuthTokensChecker: RefreshAuthTokensCheckerType) { | ||
self.networkClient = networkClient | ||
self.refreshAuthTokensChecker = refreshAuthTokensChecker | ||
} | ||
|
||
func callAsFunction(dipToken: String, completion: @escaping Completion) { | ||
refreshAuthTokensChecker.refreshIfNeeded { [weak self] error in | ||
guard let self else { return } | ||
if let error { | ||
completion(.failure(error)) | ||
} else { | ||
networkClient.executeRequest(with: makeConfiguration(dipToken: dipToken)) { error, response in | ||
if let error { | ||
self.handleErrorResponse(error, completion: completion) | ||
return | ||
} | ||
|
||
completion(.success(())) | ||
} | ||
} | ||
} | ||
} | ||
|
||
private func makeConfiguration(dipToken: String) -> RenewDedicatedIPRequestConfiguration { | ||
var configuration = RenewDedicatedIPRequestConfiguration() | ||
|
||
let bodyDataDict = ["token": dipToken] | ||
|
||
if let bodyData = try? JSONEncoder().encode(bodyDataDict) { | ||
configuration.body = bodyData | ||
} | ||
|
||
return configuration | ||
} | ||
|
||
private func handleErrorResponse(_ error: NetworkRequestError, completion: @escaping RenewDedicatedIPUseCaseType.Completion) { | ||
switch error { | ||
case .allConnectionAttemptsFailed(let statusCode): | ||
completion(.failure(statusCode == 401 ? NetworkRequestError.unauthorized : error)) | ||
return | ||
case .connectionError(statusCode: let statusCode, message: let message): | ||
completion(.failure(statusCode == 401 ? NetworkRequestError.unauthorized : error)) | ||
return | ||
default: | ||
completion(.failure(error)) | ||
} | ||
} | ||
} |
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
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
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,8 @@ | ||
|
||
import Foundation | ||
|
||
class MockDedicatedIPServerMapper: DedicatedIPServerMapperType { | ||
func map(dedicatedIps: [DedicatedIPInformation]) -> Result<[Server], ClientError> { | ||
return .success([]) | ||
} | ||
} |
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 | ||
|
||
class MockGetDedicatedIPsUseCase: GetDedicatedIPsUseCaseType { | ||
func callAsFunction(dipTokens: [String], completion: @escaping Completion) {} | ||
} |
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 | ||
|
||
class MockRenewDedicatedIPUseCase: RenewDedicatedIPUseCaseType { | ||
func callAsFunction(dipToken: String, completion: @escaping Completion) {} | ||
} |
Oops, something went wrong.