TF-2384 Handle refresh token when token expired in NSE
Signed-off-by: dab246 <tdvu@linagora.com> (cherry picked from commit c8a39bb42792770c001ac90d6301d53126225d95)
This commit is contained in:
@@ -38,11 +38,24 @@
|
||||
F5BBBF512B2EEC37007930E1 /* NetworkExceptions.swift in Sources */ = {isa = PBXBuildFile; fileRef = F5BBBF502B2EEC37007930E1 /* NetworkExceptions.swift */; };
|
||||
F5BBBF532B2EECAA007930E1 /* JmapExceptions.swift in Sources */ = {isa = PBXBuildFile; fileRef = F5BBBF522B2EECAA007930E1 /* JmapExceptions.swift */; };
|
||||
F5BBBF552B2EEF3D007930E1 /* BundleExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = F5BBBF542B2EEF3D007930E1 /* BundleExtension.swift */; };
|
||||
F5CFE0302B335098005A90A9 /* TwakeLogger.swift in Sources */ = {isa = PBXBuildFile; fileRef = F5EFC07C2B328B9F00829056 /* TwakeLogger.swift */; };
|
||||
F5CFE0352B335322005A90A9 /* (null) in Frameworks */ = {isa = PBXBuildFile; };
|
||||
F5D4EA032B2ABF090090DDFC /* NotificationService.swift in Sources */ = {isa = PBXBuildFile; fileRef = F5D4EA022B2ABF090090DDFC /* NotificationService.swift */; };
|
||||
F5D4EA072B2ABF090090DDFC /* TwakeMailNSE.appex in Embed App Extensions */ = {isa = PBXBuildFile; fileRef = F5D4EA002B2ABF090090DDFC /* TwakeMailNSE.appex */; settings = {ATTRIBUTES = (RemoveHeadersOnCopy, ); }; };
|
||||
F5E7D8742B3578F90009BB8A /* JmapConstants.swift in Sources */ = {isa = PBXBuildFile; fileRef = F53D1E7E2B2E3C2600051FD0 /* JmapConstants.swift */; };
|
||||
F5E7D8792B38763B0009BB8A /* DateExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = F5E7D8782B38763B0009BB8A /* DateExtensions.swift */; };
|
||||
F5E7D87A2B38763B0009BB8A /* DateExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = F5E7D8782B38763B0009BB8A /* DateExtensions.swift */; };
|
||||
F5E7D87C2B38764F0009BB8A /* StringExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = F5E7D87B2B38764F0009BB8A /* StringExtensions.swift */; };
|
||||
F5E7D87D2B38764F0009BB8A /* StringExtensions.swift in Sources */ = {isa = PBXBuildFile; fileRef = F5E7D87B2B38764F0009BB8A /* StringExtensions.swift */; };
|
||||
F5E7D8802B3876DE0009BB8A /* Authentication.swift in Sources */ = {isa = PBXBuildFile; fileRef = F5E7D87F2B3876DE0009BB8A /* Authentication.swift */; };
|
||||
F5E7D8822B3876F60009BB8A /* AuthenticationCredential.swift in Sources */ = {isa = PBXBuildFile; fileRef = F5E7D8812B3876F60009BB8A /* AuthenticationCredential.swift */; };
|
||||
F5E7D8842B3877050009BB8A /* AuthenticationSSO.swift in Sources */ = {isa = PBXBuildFile; fileRef = F5E7D8832B3877050009BB8A /* AuthenticationSSO.swift */; };
|
||||
F5E7D8862B3877390009BB8A /* TokenResponse.swift in Sources */ = {isa = PBXBuildFile; fileRef = F5E7D8852B3877390009BB8A /* TokenResponse.swift */; };
|
||||
F5E7D8882B38775C0009BB8A /* TokenRefreshManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = F5E7D8872B38775C0009BB8A /* TokenRefreshManager.swift */; };
|
||||
F5E7D88A2B3877D30009BB8A /* NSEPusher.swift in Sources */ = {isa = PBXBuildFile; fileRef = F5E7D8892B3877D30009BB8A /* NSEPusher.swift */; };
|
||||
F5E7D88D2B3878580009BB8A /* AuthenticationInterceptor.swift in Sources */ = {isa = PBXBuildFile; fileRef = F5E7D88C2B3878580009BB8A /* AuthenticationInterceptor.swift */; };
|
||||
F5EFC07D2B328B9F00829056 /* TwakeLogger.swift in Sources */ = {isa = PBXBuildFile; fileRef = F5EFC07C2B328B9F00829056 /* TwakeLogger.swift */; };
|
||||
F5EFC07E2B328B9F00829056 /* TwakeLogger.swift in Sources */ = {isa = PBXBuildFile; fileRef = F5EFC07C2B328B9F00829056 /* TwakeLogger.swift */; };
|
||||
F5EFC07F2B3292FA00829056 /* TwakeLogger.swift in Sources */ = {isa = PBXBuildFile; fileRef = F5EFC07C2B328B9F00829056 /* TwakeLogger.swift */; };
|
||||
/* End PBXBuildFile section */
|
||||
|
||||
/* Begin PBXContainerItemProxy section */
|
||||
@@ -135,6 +148,15 @@
|
||||
F5D4EA022B2ABF090090DDFC /* NotificationService.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NotificationService.swift; sourceTree = "<group>"; };
|
||||
F5D4EA042B2ABF090090DDFC /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||
F5D4EA0C2B2ABF970090DDFC /* TwakeMailNSE.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = TwakeMailNSE.entitlements; sourceTree = "<group>"; };
|
||||
F5E7D8782B38763B0009BB8A /* DateExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DateExtensions.swift; sourceTree = "<group>"; };
|
||||
F5E7D87B2B38764F0009BB8A /* StringExtensions.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StringExtensions.swift; sourceTree = "<group>"; };
|
||||
F5E7D87F2B3876DE0009BB8A /* Authentication.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Authentication.swift; sourceTree = "<group>"; };
|
||||
F5E7D8812B3876F60009BB8A /* AuthenticationCredential.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthenticationCredential.swift; sourceTree = "<group>"; };
|
||||
F5E7D8832B3877050009BB8A /* AuthenticationSSO.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthenticationSSO.swift; sourceTree = "<group>"; };
|
||||
F5E7D8852B3877390009BB8A /* TokenResponse.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TokenResponse.swift; sourceTree = "<group>"; };
|
||||
F5E7D8872B38775C0009BB8A /* TokenRefreshManager.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TokenRefreshManager.swift; sourceTree = "<group>"; };
|
||||
F5E7D8892B3877D30009BB8A /* NSEPusher.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NSEPusher.swift; sourceTree = "<group>"; };
|
||||
F5E7D88C2B3878580009BB8A /* AuthenticationInterceptor.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AuthenticationInterceptor.swift; sourceTree = "<group>"; };
|
||||
F5EFC07C2B328B9F00829056 /* TwakeLogger.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TwakeLogger.swift; sourceTree = "<group>"; };
|
||||
/* End PBXFileReference section */
|
||||
|
||||
@@ -144,6 +166,7 @@
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
CDFECA8C54311B749F044831 /* Pods_Runner.framework in Frameworks */,
|
||||
F5CFE0352B335322005A90A9 /* (null) in Frameworks */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
@@ -240,8 +263,10 @@
|
||||
F53D1E642B2DE84E00051FD0 /* Network */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
F5E7D8762B385B430009BB8A /* Model */,
|
||||
F5E7D88B2B3878470009BB8A /* Interceptor */,
|
||||
F5CFE0182B334BCD005A90A9 /* Model */,
|
||||
F53D1E652B2DE8B800051FD0 /* AlamofireService.swift */,
|
||||
F5E7D8872B38775C0009BB8A /* TokenRefreshManager.swift */,
|
||||
);
|
||||
path = Network;
|
||||
sourceTree = "<group>";
|
||||
@@ -324,6 +349,7 @@
|
||||
F5630C7D2B2CE359003CC0FD /* InfoPlistReader.swift */,
|
||||
F5BBBF542B2EEF3D007930E1 /* BundleExtension.swift */,
|
||||
F5630C862B2D0387003CC0FD /* PayloadParser.swift */,
|
||||
F5E7D8892B3877D30009BB8A /* NSEPusher.swift */,
|
||||
);
|
||||
path = Utils;
|
||||
sourceTree = "<group>";
|
||||
@@ -337,6 +363,19 @@
|
||||
path = Exceptions;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
F5CFE0182B334BCD005A90A9 /* Model */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
F53D1E892B2E4BB700051FD0 /* TokenOidc.swift */,
|
||||
F53D1E872B2E4B3B00051FD0 /* AuthenticationType.swift */,
|
||||
F5E7D87F2B3876DE0009BB8A /* Authentication.swift */,
|
||||
F5E7D8812B3876F60009BB8A /* AuthenticationCredential.swift */,
|
||||
F5E7D8832B3877050009BB8A /* AuthenticationSSO.swift */,
|
||||
F5E7D8852B3877390009BB8A /* TokenResponse.swift */,
|
||||
);
|
||||
path = Model;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
F5D4EA012B2ABF090090DDFC /* TwakeMailNSE */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
@@ -350,13 +389,21 @@
|
||||
path = TwakeMailNSE;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
F5E7D8762B385B430009BB8A /* Model */ = {
|
||||
F5E7D8772B38761B0009BB8A /* Extensions */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
F53D1E872B2E4B3B00051FD0 /* AuthenticationType.swift */,
|
||||
F53D1E892B2E4BB700051FD0 /* TokenOidc.swift */,
|
||||
F5E7D8782B38763B0009BB8A /* DateExtensions.swift */,
|
||||
F5E7D87B2B38764F0009BB8A /* StringExtensions.swift */,
|
||||
);
|
||||
path = Model;
|
||||
path = Extensions;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
F5E7D88B2B3878470009BB8A /* Interceptor */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
F5E7D88C2B3878580009BB8A /* AuthenticationInterceptor.swift */,
|
||||
);
|
||||
path = Interceptor;
|
||||
sourceTree = "<group>";
|
||||
};
|
||||
F5EFC07B2B328B8600829056 /* Logger */ = {
|
||||
@@ -370,6 +417,7 @@
|
||||
F5EFC0802B32965100829056 /* TwakeCore */ = {
|
||||
isa = PBXGroup;
|
||||
children = (
|
||||
F5E7D8772B38761B0009BB8A /* Extensions */,
|
||||
F5BBBF4F2B2EEC1B007930E1 /* Exceptions */,
|
||||
F53D1E642B2DE84E00051FD0 /* Network */,
|
||||
F5EFC07B2B328B8600829056 /* Logger */,
|
||||
@@ -412,6 +460,8 @@
|
||||
F5D4EA062B2ABF090090DDFC /* PBXTargetDependency */,
|
||||
);
|
||||
name = Runner;
|
||||
packageProductDependencies = (
|
||||
);
|
||||
productName = Runner;
|
||||
productReference = 97C146EE1CF9000F007C117D /* Runner.app */;
|
||||
productType = "com.apple.product-type.application";
|
||||
@@ -607,9 +657,12 @@
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
F5E7D87C2B38764F0009BB8A /* StringExtensions.swift in Sources */,
|
||||
74858FAF1ED2DC5600515810 /* AppDelegate.swift in Sources */,
|
||||
F5E7D8792B38763B0009BB8A /* DateExtensions.swift in Sources */,
|
||||
1498D2341E8E89220040F4C2 /* GeneratedPluginRegistrant.m in Sources */,
|
||||
F5EFC07D2B328B9F00829056 /* TwakeLogger.swift in Sources */,
|
||||
F5E7D8742B3578F90009BB8A /* JmapConstants.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
@@ -617,7 +670,7 @@
|
||||
isa = PBXSourcesBuildPhase;
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
F5EFC07F2B3292FA00829056 /* TwakeLogger.swift in Sources */,
|
||||
F5CFE0302B335098005A90A9 /* TwakeLogger.swift in Sources */,
|
||||
F52F993027FD6EB900346091 /* ShareViewController.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
@@ -628,15 +681,22 @@
|
||||
files = (
|
||||
F5BBBF512B2EEC37007930E1 /* NetworkExceptions.swift in Sources */,
|
||||
F53D1E7F2B2E3C2600051FD0 /* JmapConstants.swift in Sources */,
|
||||
F5E7D8822B3876F60009BB8A /* AuthenticationCredential.swift in Sources */,
|
||||
F5D4EA032B2ABF090090DDFC /* NotificationService.swift in Sources */,
|
||||
F53D1E8A2B2E4BB700051FD0 /* TokenOidc.swift in Sources */,
|
||||
F53D1E862B2E401B00051FD0 /* JmapRequestGenerator.swift in Sources */,
|
||||
F5E7D87A2B38763B0009BB8A /* DateExtensions.swift in Sources */,
|
||||
F5E7D88D2B3878580009BB8A /* AuthenticationInterceptor.swift in Sources */,
|
||||
F5E7D88A2B3877D30009BB8A /* NSEPusher.swift in Sources */,
|
||||
F5630C7E2B2CE359003CC0FD /* InfoPlistReader.swift in Sources */,
|
||||
F53D1E662B2DE8B800051FD0 /* AlamofireService.swift in Sources */,
|
||||
F5BBBF532B2EECAA007930E1 /* JmapExceptions.swift in Sources */,
|
||||
F53D1E882B2E4B3B00051FD0 /* AuthenticationType.swift in Sources */,
|
||||
F53D1E6C2B2E208C00051FD0 /* Email.swift in Sources */,
|
||||
F5630C872B2D0387003CC0FD /* PayloadParser.swift in Sources */,
|
||||
F5E7D8862B3877390009BB8A /* TokenResponse.swift in Sources */,
|
||||
F5E7D8842B3877050009BB8A /* AuthenticationSSO.swift in Sources */,
|
||||
F5E7D87D2B38764F0009BB8A /* StringExtensions.swift in Sources */,
|
||||
F53D1E782B2E2C7E00051FD0 /* JmapResponseObject.swift in Sources */,
|
||||
F5630C852B2CF50C003CC0FD /* TypeName.swift in Sources */,
|
||||
F5630C7B2B2CE148003CC0FD /* KeychainSharingSession.swift in Sources */,
|
||||
@@ -644,9 +704,11 @@
|
||||
F5BBBF552B2EEF3D007930E1 /* BundleExtension.swift in Sources */,
|
||||
F53D1E7C2B2E35CC00051FD0 /* EmailAddress.swift in Sources */,
|
||||
F5EFC07E2B328B9F00829056 /* TwakeLogger.swift in Sources */,
|
||||
F5E7D8882B38775C0009BB8A /* TokenRefreshManager.swift in Sources */,
|
||||
F5630C762B2CDFDA003CC0FD /* KeychainController.swift in Sources */,
|
||||
F53D1E832B2E3D4600051FD0 /* JmapRequestObject.swift in Sources */,
|
||||
F5630C782B2CE091003CC0FD /* KeyChainControllerDelegate.swift in Sources */,
|
||||
F5E7D8802B3876DE0009BB8A /* Authentication.swift in Sources */,
|
||||
);
|
||||
runOnlyForDeploymentPostprocessing = 0;
|
||||
};
|
||||
|
||||
@@ -84,8 +84,13 @@ import flutter_local_notifications
|
||||
TwakeLogger.shared.log(message: "AppDelegate::userNotificationCenter::willPresent:newBadgeCount: \(newBadgeCount)")
|
||||
updateAppBadger(currentBadgeCount: newBadgeCount)
|
||||
}
|
||||
|
||||
completionHandler(isAppForegroundActive() ? [] : [.alert, .badge, .sound])
|
||||
if let emailId = notification.request.content.userInfo[JmapConstants.EMAIL_ID] as? String,
|
||||
!emailId.isEmpty,
|
||||
!isAppForegroundActive() {
|
||||
completionHandler([.alert, .badge, .sound])
|
||||
} else {
|
||||
completionHandler([])
|
||||
}
|
||||
}
|
||||
|
||||
override func userNotificationCenter(_ center: UNUserNotificationCenter, didReceive response: UNNotificationResponse, withCompletionHandler completionHandler: @escaping () -> Void) {
|
||||
|
||||
@@ -0,0 +1,7 @@
|
||||
import Foundation
|
||||
|
||||
extension Date {
|
||||
func isBefore(_ otherDate: Date) -> Bool {
|
||||
return self < otherDate
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
import Foundation
|
||||
|
||||
extension String {
|
||||
func convertISO8601StringToDate() -> Date? {
|
||||
let dateFormatter = ISO8601DateFormatter()
|
||||
dateFormatter.formatOptions = [.withInternetDateTime, .withFractionalSeconds]
|
||||
if let date = dateFormatter.date(from: self) {
|
||||
return date
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
@@ -3,13 +3,13 @@ import Alamofire
|
||||
|
||||
class JmapClient {
|
||||
static let shared: JmapClient = JmapClient()
|
||||
|
||||
|
||||
private let jmapHeader = HTTPHeader(name: "Accept", value: "application/json;jmapVersion=rfc-8621")
|
||||
|
||||
private func getBasicAuthorization(basicAuth: String) -> String {
|
||||
return "Basic \(basicAuth)"
|
||||
}
|
||||
|
||||
|
||||
private var authentication: Authentication?
|
||||
private var tokenRefreshManager: TokenRefreshManager?
|
||||
private var authenticationInterceptor: AuthenticationInterceptor?
|
||||
|
||||
func getNewEmails(
|
||||
apiUrl: String,
|
||||
accountId: String,
|
||||
@@ -17,27 +17,47 @@ class JmapClient {
|
||||
authenticationType: AuthenticationType,
|
||||
tokenOidc: TokenOidc?,
|
||||
basicAuth: String?,
|
||||
tokenEndpointUrl: String?,
|
||||
oidcScopes: [String]?,
|
||||
onSuccess: @escaping ([Email]) -> Void,
|
||||
onFailure: @escaping (Error) -> Void
|
||||
) {
|
||||
let authenticationValue = authenticationType == AuthenticationType.basic
|
||||
? getBasicAuthorization(basicAuth: basicAuth ?? "")
|
||||
: tokenOidc?.getAuthorization() ?? ""
|
||||
|
||||
let headers = HTTPHeaders([
|
||||
jmapHeader,
|
||||
HTTPHeader(name: "Authorization", value: authenticationValue)
|
||||
])
|
||||
|
||||
if (authenticationType == AuthenticationType.basic) {
|
||||
authentication = AuthenticationCredential(
|
||||
type: AuthenticationType.basic,
|
||||
basicAuth: basicAuth ?? ""
|
||||
)
|
||||
} else {
|
||||
authentication = AuthenticationSSO(
|
||||
type: AuthenticationType.oidc,
|
||||
accessToken: tokenOidc?.token ?? "",
|
||||
refreshToken: tokenOidc?.refreshToken ?? "",
|
||||
expireTime: tokenOidc?.expiredTime
|
||||
)
|
||||
|
||||
tokenRefreshManager = TokenRefreshManager(
|
||||
refreshToken: tokenOidc?.refreshToken ?? "",
|
||||
tokenEndpoint: tokenEndpointUrl ?? "",
|
||||
scopes: oidcScopes
|
||||
)
|
||||
}
|
||||
|
||||
authenticationInterceptor = AuthenticationInterceptor(
|
||||
authentication: authentication,
|
||||
accountId: accountId,
|
||||
tokenRefreshManager: tokenRefreshManager
|
||||
)
|
||||
|
||||
let jmapRequestObject = JmapRequestGenerator.shared.createEmailChangesRequest(
|
||||
accountId: accountId,
|
||||
sinceState: sinceState
|
||||
)
|
||||
|
||||
|
||||
AlamofireService.shared.post(
|
||||
url: apiUrl,
|
||||
payloadData: jmapRequestObject?.toData(),
|
||||
headers: headers,
|
||||
headers: HTTPHeaders([jmapHeader]),
|
||||
interceptor: authenticationInterceptor,
|
||||
onSuccess: { (data: JmapResponseObject<Email>) in
|
||||
if let listEmail = data.parsing(methodName: JmapConstants.EMAIL_GET_METHOD_NAME, methodCallId: "c1"), !listEmail.isEmpty {
|
||||
onSuccess(listEmail)
|
||||
|
||||
@@ -15,4 +15,6 @@ class JmapConstants {
|
||||
"preview",
|
||||
"from"
|
||||
]
|
||||
|
||||
static let EMAIL_ID = "email_id"
|
||||
}
|
||||
|
||||
@@ -8,6 +8,7 @@ class AlamofireService {
|
||||
url: String,
|
||||
payloadData: Data?,
|
||||
headers: HTTPHeaders? = nil,
|
||||
interceptor: RequestInterceptor? = nil,
|
||||
onSuccess: @escaping (T) -> Void,
|
||||
onFailure: @escaping (Error) -> Void
|
||||
) {
|
||||
@@ -18,7 +19,7 @@ class AlamofireService {
|
||||
|
||||
request.httpBody = payloadData
|
||||
|
||||
AF.request(request).responseDecodable(of: T.self) { response in
|
||||
AF.request(request, interceptor: interceptor).validate().responseDecodable(of: T.self) { response in
|
||||
switch(response.result) {
|
||||
case .success(let data):
|
||||
onSuccess(data)
|
||||
|
||||
@@ -0,0 +1,81 @@
|
||||
import Alamofire
|
||||
import Foundation
|
||||
|
||||
class AuthenticationInterceptor: RequestInterceptor {
|
||||
var authentication: Authentication?
|
||||
let accountId: String
|
||||
var tokenRefreshManager: TokenRefreshManager?
|
||||
|
||||
private lazy var keychainController = KeychainController(service: .sessions,
|
||||
accessGroup: InfoPlistReader.main.keychainAccessGroupIdentifier)
|
||||
|
||||
init(authentication: Authentication?, accountId: String, tokenRefreshManager: TokenRefreshManager?) {
|
||||
self.authentication = authentication
|
||||
self.accountId = accountId
|
||||
self.tokenRefreshManager = tokenRefreshManager
|
||||
}
|
||||
|
||||
// MARK: - Adapt: Called before request to set request headers
|
||||
func adapt(_ urlRequest: URLRequest, for session: Session, completion: @escaping (Result<URLRequest, Error>) -> Void) {
|
||||
var modifiedRequest = urlRequest
|
||||
|
||||
if (authentication != nil) {
|
||||
modifiedRequest.addValue(authentication!.getAuthenticationHeader(), forHTTPHeaderField: "Authorization")
|
||||
}
|
||||
|
||||
completion(.success(modifiedRequest))
|
||||
}
|
||||
|
||||
// MARK: - Retry: Called when status code is not 200...299 or in failure
|
||||
func retry(_ request: Request, for session: Session, dueTo error: Error, completion: @escaping (RetryResult) -> Void) {
|
||||
guard let response = request.task?.response as? HTTPURLResponse,
|
||||
let authenticationSSO = authentication as? AuthenticationSSO,
|
||||
validateToRefreshToken(response: response, authenticationSSO: authenticationSSO),
|
||||
let tokenRefreshManager else {
|
||||
return completion(.doNotRetryWithError(error))
|
||||
}
|
||||
|
||||
handleRefreshToken(tokenRefreshManager: tokenRefreshManager) { tokenResponse in
|
||||
guard let accessToken = tokenResponse.accessToken,
|
||||
let refreshToken = tokenResponse.refreshToken else {
|
||||
return completion(.doNotRetryWithError(error))
|
||||
}
|
||||
|
||||
self.authentication = AuthenticationSSO(
|
||||
type: AuthenticationType.oidc,
|
||||
accessToken: accessToken,
|
||||
refreshToken: refreshToken,
|
||||
expireTime: "\(tokenResponse.expiresTime ?? 0)"
|
||||
)
|
||||
|
||||
self.keychainController.updateTokenOidc(
|
||||
accountId: self.accountId,
|
||||
newTokenOidc: TokenOidc(
|
||||
token: accessToken,
|
||||
tokenId: tokenResponse.tokenId,
|
||||
expiredTime: "\(tokenResponse.expiresTime ?? 0)",
|
||||
refreshToken: refreshToken
|
||||
)
|
||||
)
|
||||
|
||||
return completion(.retry)
|
||||
} onFailure: { error in
|
||||
return completion(.doNotRetryWithError(error))
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
// MARK: - Check the conditions to perform token refresh
|
||||
private func validateToRefreshToken(response: HTTPURLResponse, authenticationSSO: AuthenticationSSO) -> Bool {
|
||||
return response.statusCode == 401 &&
|
||||
!authenticationSSO.refreshToken.isEmpty &&
|
||||
authenticationSSO.isExpiredTime()
|
||||
}
|
||||
|
||||
// MARK: - Handle refresh token to get new token
|
||||
private func handleRefreshToken(tokenRefreshManager: TokenRefreshManager,
|
||||
onSuccess: @escaping (TokenResponse) -> Void,
|
||||
onFailure: @escaping (Error) -> Void) {
|
||||
tokenRefreshManager.handleRefreshAccessToken(onSuccess: onSuccess, onFailure: onFailure)
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
import Foundation
|
||||
|
||||
protocol Authentication {
|
||||
var type: AuthenticationType { get }
|
||||
|
||||
func getAuthenticationHeader() -> String
|
||||
}
|
||||
@@ -0,0 +1,16 @@
|
||||
import Foundation
|
||||
|
||||
struct AuthenticationCredential: Authentication {
|
||||
var type: AuthenticationType
|
||||
|
||||
let basicAuth: String
|
||||
|
||||
init(type: AuthenticationType, basicAuth: String) {
|
||||
self.type = type
|
||||
self.basicAuth = basicAuth
|
||||
}
|
||||
|
||||
func getAuthenticationHeader() -> String {
|
||||
return "Basic \(basicAuth)"
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,33 @@
|
||||
import Foundation
|
||||
|
||||
struct AuthenticationSSO: Authentication {
|
||||
var type: AuthenticationType
|
||||
|
||||
let accessToken: String
|
||||
let refreshToken: String
|
||||
let expireTime: String?
|
||||
|
||||
init(type: AuthenticationType, accessToken: String, refreshToken: String, expireTime: String?) {
|
||||
self.type = type
|
||||
self.accessToken = accessToken
|
||||
self.refreshToken = refreshToken
|
||||
self.expireTime = expireTime
|
||||
}
|
||||
|
||||
func getAuthenticationHeader() -> String {
|
||||
return "Bearer \(accessToken)"
|
||||
}
|
||||
|
||||
func isExpiredTime() -> Bool {
|
||||
guard let expireTime else {
|
||||
return false
|
||||
}
|
||||
|
||||
guard let expireDate = expireTime.convertISO8601StringToDate(),
|
||||
expireDate.isBefore(Date.now) else {
|
||||
return true
|
||||
}
|
||||
|
||||
return false
|
||||
}
|
||||
}
|
||||
@@ -2,13 +2,7 @@ import Foundation
|
||||
|
||||
struct TokenOidc: Codable {
|
||||
let token: String
|
||||
let tokenId: String
|
||||
let tokenId: String?
|
||||
let expiredTime: String?
|
||||
let refreshToken: String
|
||||
}
|
||||
|
||||
extension TokenOidc {
|
||||
func getAuthorization() -> String {
|
||||
return "Bearer \(token)"
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,25 @@
|
||||
import Foundation
|
||||
|
||||
struct TokenResponse: Codable {
|
||||
let accessToken: String?
|
||||
let refreshToken: String?
|
||||
let expiresTime: Int?
|
||||
let refreshExpiresIn: Int?
|
||||
let tokenId: String?
|
||||
let tokenType: String?
|
||||
let scope: String?
|
||||
let sessionState: String?
|
||||
let error: String?
|
||||
|
||||
enum CodingKeys: String, CodingKey {
|
||||
case accessToken = "access_token"
|
||||
case refreshToken = "refresh_token"
|
||||
case expiresTime = "expires_in"
|
||||
case refreshExpiresIn = "refresh_expires_in"
|
||||
case tokenId = "id_token"
|
||||
case tokenType = "token_type"
|
||||
case scope
|
||||
case sessionState = "session_state"
|
||||
case error
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,69 @@
|
||||
import Foundation
|
||||
import Alamofire
|
||||
|
||||
class TokenRefreshManager {
|
||||
private let MOBIE_CLIENT_ID = "teammail-mobile"
|
||||
private let MOBIE_REDIRECT_URL = "teammail.mobile://oauthredirect"
|
||||
private let OIDC_SCOPES = ["openid", "profile", "email", "offline_access"]
|
||||
|
||||
private let GRANT_TYPE = "grant_type"
|
||||
private let REFRESH_TOKEN = "refresh_token"
|
||||
private let CLIENT_ID = "client_id"
|
||||
private let REDIRECT_URI = "redirect_uri"
|
||||
private let SCOPES = "scopes"
|
||||
|
||||
let refreshToken: String
|
||||
let tokenEndpoint: String
|
||||
let scopes: [String]?
|
||||
|
||||
init(refreshToken: String, tokenEndpoint: String, scopes: [String]?) {
|
||||
self.refreshToken = refreshToken
|
||||
self.tokenEndpoint = tokenEndpoint
|
||||
self.scopes = scopes
|
||||
}
|
||||
|
||||
private func getScopes() -> String {
|
||||
if let scopes, !scopes.isEmpty {
|
||||
return scopes.joined(separator: " ")
|
||||
}
|
||||
return OIDC_SCOPES.joined(separator: " ")
|
||||
}
|
||||
|
||||
func handleRefreshAccessToken(
|
||||
onSuccess: @escaping (TokenResponse) -> Void,
|
||||
onFailure: @escaping (Error) -> Void
|
||||
) {
|
||||
guard let tokenEndpointUrl = URL(string: tokenEndpoint),
|
||||
var request = try? URLRequest(url: tokenEndpointUrl, method: .post) else {
|
||||
return onFailure(NetworkExceptions.requestUrlInvalid)
|
||||
}
|
||||
|
||||
let params = [
|
||||
CLIENT_ID: MOBIE_CLIENT_ID,
|
||||
GRANT_TYPE: REFRESH_TOKEN,
|
||||
REDIRECT_URI: MOBIE_REDIRECT_URL,
|
||||
REFRESH_TOKEN: refreshToken,
|
||||
SCOPES: getScopes(),
|
||||
]
|
||||
|
||||
let data = params
|
||||
.map { "\($0)=\($1)" }
|
||||
.joined(separator: "&")
|
||||
.data(using: .utf8)
|
||||
|
||||
request.httpBody = data
|
||||
|
||||
AF.request(request).responseDecodable(of: TokenResponse.self) { response in
|
||||
if (response.response?.statusCode != 200) {
|
||||
onFailure(NetworkExceptions(value: "Failed to get token: \(response.error?.localizedDescription ?? "Unknown Error")"))
|
||||
} else {
|
||||
switch(response.result) {
|
||||
case .success(let data):
|
||||
onSuccess(data)
|
||||
case .failure(let error):
|
||||
onFailure(NetworkExceptions(value: "Failed to get token \(error.localizedDescription)"))
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -48,4 +48,13 @@ class KeychainController: KeychainControllerDelegate {
|
||||
}
|
||||
} catch {}
|
||||
}
|
||||
|
||||
func updateTokenOidc(accountId: String, newTokenOidc: TokenOidc) {
|
||||
do {
|
||||
if let sharingSession = retrieveSharingSessionFromKeychain(accountId: accountId) {
|
||||
let newSharingSession = sharingSession.updateTokenOidc(newTokenOidc: newTokenOidc)
|
||||
try keychain.set(newSharingSession.toJson() ?? "", key: accountId)
|
||||
}
|
||||
} catch {}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -8,6 +8,8 @@ struct KeychainSharingSession: Codable {
|
||||
let emailState: String?
|
||||
let tokenOIDC: TokenOidc?
|
||||
let basicAuth: String?
|
||||
let tokenEndpoint: String?
|
||||
let oidcScopes: [String]?
|
||||
}
|
||||
|
||||
extension KeychainSharingSession {
|
||||
@@ -19,7 +21,28 @@ extension KeychainSharingSession {
|
||||
apiUrl: self.apiUrl,
|
||||
emailState: newState,
|
||||
tokenOIDC: self.tokenOIDC,
|
||||
basicAuth: self.basicAuth
|
||||
basicAuth: self.basicAuth,
|
||||
tokenEndpoint: self.tokenEndpoint,
|
||||
oidcScopes: self.oidcScopes
|
||||
)
|
||||
}
|
||||
|
||||
func updateTokenOidc(newTokenOidc: TokenOidc) -> KeychainSharingSession {
|
||||
return KeychainSharingSession(
|
||||
accountId: self.accountId,
|
||||
userName: self.userName,
|
||||
authenticationType: self.authenticationType,
|
||||
apiUrl: self.apiUrl,
|
||||
emailState: self.emailState,
|
||||
tokenOIDC: TokenOidc(
|
||||
token: newTokenOidc.token,
|
||||
tokenId: newTokenOidc.tokenId,
|
||||
expiredTime: newTokenOidc.expiredTime,
|
||||
refreshToken: newTokenOidc.refreshToken
|
||||
),
|
||||
basicAuth: self.basicAuth,
|
||||
tokenEndpoint: self.tokenEndpoint,
|
||||
oidcScopes: self.oidcScopes
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@@ -61,6 +61,8 @@ class NotificationService: UNNotificationServiceExtension {
|
||||
authenticationType: keychainSharingSession.authenticationType,
|
||||
tokenOidc: keychainSharingSession.tokenOIDC,
|
||||
basicAuth: keychainSharingSession.basicAuth,
|
||||
tokenEndpointUrl: keychainSharingSession.tokenEndpoint,
|
||||
oidcScopes: keychainSharingSession.oidcScopes,
|
||||
onSuccess: { emails in
|
||||
self.keychainController.updateEmailStateToKeychain(accountId: keychainSharingSession.accountId, newState: newEmailState)
|
||||
|
||||
@@ -90,7 +92,7 @@ class NotificationService: UNNotificationServiceExtension {
|
||||
}
|
||||
},
|
||||
onFailure: { error in
|
||||
self.modifiedContent?.body = newEmailDefaultMessage
|
||||
self.modifiedContent?.body = self.newEmailDefaultMessage
|
||||
self.modifiedContent?.badge = NSNumber(value: 1)
|
||||
return self.notify()
|
||||
}
|
||||
|
||||
@@ -20,6 +20,8 @@ class KeychainSharingSession with EquatableMixin {
|
||||
String? emailState;
|
||||
TokenOIDC? tokenOIDC;
|
||||
String? basicAuth;
|
||||
String? tokenEndpoint;
|
||||
List<String>? oidcScopes;
|
||||
|
||||
KeychainSharingSession({
|
||||
required this.accountId,
|
||||
@@ -29,6 +31,8 @@ class KeychainSharingSession with EquatableMixin {
|
||||
this.emailState,
|
||||
this.tokenOIDC,
|
||||
this.basicAuth,
|
||||
this.tokenEndpoint,
|
||||
this.oidcScopes,
|
||||
});
|
||||
|
||||
factory KeychainSharingSession.fromJson(Map<String, dynamic> json) => _$KeychainSharingSessionFromJson(json);
|
||||
@@ -44,5 +48,7 @@ class KeychainSharingSession with EquatableMixin {
|
||||
emailState,
|
||||
tokenOIDC,
|
||||
basicAuth,
|
||||
tokenEndpoint,
|
||||
oidcScopes,
|
||||
];
|
||||
}
|
||||
}
|
||||
@@ -6,8 +6,13 @@ part 'oidc_discovery_response.g.dart';
|
||||
@JsonSerializable(explicitToJson: true, includeIfNull: false)
|
||||
class OIDCDiscoveryResponse with EquatableMixin {
|
||||
|
||||
@JsonKey(name: 'authorization_endpoint')
|
||||
final String? authorizationEndpoint;
|
||||
|
||||
@JsonKey(name: 'token_endpoint')
|
||||
final String? tokenEndpoint;
|
||||
|
||||
@JsonKey(name: 'end_session_endpoint')
|
||||
final String? endSessionEndpoint;
|
||||
|
||||
OIDCDiscoveryResponse(this.authorizationEndpoint, this.tokenEndpoint, this.endSessionEndpoint);
|
||||
|
||||
Reference in New Issue
Block a user