TF-3833 Fix failed to keep mobile app connect after network issue
Signed-off-by: dab246 <tdvu@linagora.com>
This commit is contained in:
@@ -1,4 +1,3 @@
|
||||
|
||||
import 'package:model/oidc/oidc_configuration.dart';
|
||||
import 'package:model/oidc/response/oidc_discovery_response.dart';
|
||||
import 'package:model/oidc/token_id.dart';
|
||||
|
||||
+88
@@ -0,0 +1,88 @@
|
||||
import 'package:core/utils/app_logger.dart';
|
||||
import 'package:core/utils/platform_info.dart';
|
||||
import 'package:flutter_appauth_platform_interface/flutter_appauth_platform_interface.dart';
|
||||
import 'package:model/oidc/oidc_configuration.dart';
|
||||
import 'package:model/oidc/response/oidc_discovery_response.dart';
|
||||
import 'package:model/oidc/token_id.dart';
|
||||
import 'package:tmail_ui_user/features/login/domain/exceptions/oauth_authorization_error.dart';
|
||||
import 'package:tmail_ui_user/features/login/domain/extensions/oidc_configuration_extensions.dart';
|
||||
|
||||
mixin AuthenticationClientInteractionMixin {
|
||||
EndSessionRequest getEndSessionRequest(
|
||||
TokenId tokenId,
|
||||
OIDCConfiguration config,
|
||||
OIDCDiscoveryResponse discoveryResponse,
|
||||
) {
|
||||
final authorizationEndpoint = discoveryResponse.authorizationEndpoint;
|
||||
final tokenEndpoint = discoveryResponse.tokenEndpoint;
|
||||
AuthorizationServiceConfiguration? serviceConfiguration;
|
||||
|
||||
if (authorizationEndpoint != null && tokenEndpoint != null) {
|
||||
serviceConfiguration = AuthorizationServiceConfiguration(
|
||||
authorizationEndpoint: authorizationEndpoint,
|
||||
tokenEndpoint: tokenEndpoint,
|
||||
endSessionEndpoint: discoveryResponse.endSessionEndpoint,
|
||||
);
|
||||
}
|
||||
|
||||
return EndSessionRequest(
|
||||
idTokenHint: tokenId.uuid,
|
||||
postLogoutRedirectUrl: config.logoutRedirectUrl,
|
||||
discoveryUrl: config.discoveryUrl,
|
||||
serviceConfiguration: serviceConfiguration,
|
||||
externalUserAgent: getExternalUserAgent(),
|
||||
);
|
||||
}
|
||||
|
||||
ExternalUserAgent getExternalUserAgent() => PlatformInfo.isIOS
|
||||
? ExternalUserAgent.ephemeralAsWebAuthenticationSession
|
||||
: ExternalUserAgent.asWebAuthenticationSession;
|
||||
|
||||
TokenRequest getRefreshTokenRequest(
|
||||
String clientId,
|
||||
String redirectUrl,
|
||||
String discoveryUrl,
|
||||
String refreshToken,
|
||||
List<String> scopes,
|
||||
) {
|
||||
return TokenRequest(
|
||||
clientId,
|
||||
redirectUrl,
|
||||
discoveryUrl: discoveryUrl,
|
||||
refreshToken: refreshToken,
|
||||
grantType: GrantType.refreshToken,
|
||||
scopes: scopes,
|
||||
);
|
||||
}
|
||||
|
||||
AuthorizationTokenRequest getAuthorizationTokenRequest(
|
||||
String clientId,
|
||||
String redirectUrl,
|
||||
String discoveryUrl,
|
||||
List<String> scopes,
|
||||
) {
|
||||
return AuthorizationTokenRequest(
|
||||
clientId,
|
||||
redirectUrl,
|
||||
discoveryUrl: discoveryUrl,
|
||||
scopes: scopes,
|
||||
externalUserAgent: getExternalUserAgent(),
|
||||
);
|
||||
}
|
||||
|
||||
dynamic handleException(dynamic exception) {
|
||||
if (exception is FlutterAppAuthPlatformException) {
|
||||
logError('$runtimeType::handleException: ErrorDetails = ${exception.platformErrorDetails.toString()}');
|
||||
final errorCode = exception.platformErrorDetails.error;
|
||||
if (errorCode != null) {
|
||||
final oauthErrorCode = OAuthAuthorizationError.fromErrorCode(
|
||||
errorCode,
|
||||
errorDescription: exception.platformErrorDetails.errorDescription,
|
||||
);
|
||||
return oauthErrorCode;
|
||||
}
|
||||
}
|
||||
|
||||
return exception;
|
||||
}
|
||||
}
|
||||
+58
-52
@@ -10,82 +10,88 @@ import 'package:model/oidc/token_oidc.dart';
|
||||
import 'package:tmail_ui_user/features/login/data/extensions/authentication_token_extension.dart';
|
||||
import 'package:tmail_ui_user/features/login/data/extensions/token_response_extension.dart';
|
||||
import 'package:tmail_ui_user/features/login/data/network/authentication_client/authentication_client_base.dart';
|
||||
import 'package:tmail_ui_user/features/login/data/network/authentication_client/authentication_client_interaction_mixin.dart';
|
||||
import 'package:tmail_ui_user/features/login/data/network/config/oidc_constant.dart';
|
||||
import 'package:tmail_ui_user/features/login/domain/exceptions/authentication_exception.dart';
|
||||
import 'package:tmail_ui_user/features/login/domain/extensions/oidc_configuration_extensions.dart';
|
||||
|
||||
class AuthenticationClientMobile implements AuthenticationClientBase {
|
||||
class AuthenticationClientMobile with AuthenticationClientInteractionMixin
|
||||
implements AuthenticationClientBase {
|
||||
|
||||
final FlutterAppAuth _appAuth;
|
||||
|
||||
AuthenticationClientMobile(this._appAuth);
|
||||
|
||||
@override
|
||||
Future<TokenOIDC> getTokenOIDC(String clientId, String redirectUrl,
|
||||
String discoveryUrl, List<String> scopes) async {
|
||||
Future<TokenOIDC> getTokenOIDC(
|
||||
String clientId,
|
||||
String redirectUrl,
|
||||
String discoveryUrl,
|
||||
List<String> scopes,
|
||||
) async {
|
||||
final authorizationTokenRequest = getAuthorizationTokenRequest(
|
||||
clientId,
|
||||
redirectUrl,
|
||||
discoveryUrl,
|
||||
scopes,
|
||||
);
|
||||
final authorizationTokenResponse = await _appAuth.authorizeAndExchangeCode(
|
||||
AuthorizationTokenRequest(
|
||||
clientId,
|
||||
redirectUrl,
|
||||
discoveryUrl: discoveryUrl,
|
||||
scopes: scopes,
|
||||
preferEphemeralSession: true));
|
||||
|
||||
log('AuthenticationClientMobile::getTokenOIDC(): token: ${authorizationTokenResponse?.accessToken}');
|
||||
|
||||
if (authorizationTokenResponse != null) {
|
||||
final tokenOIDC = authorizationTokenResponse.toTokenOIDC();
|
||||
if (tokenOIDC.isTokenValid()) {
|
||||
return tokenOIDC;
|
||||
} else {
|
||||
throw AccessTokenInvalidException();
|
||||
}
|
||||
authorizationTokenRequest,
|
||||
);
|
||||
log('$runtimeType::getTokenOIDC(): token: ${authorizationTokenResponse.accessToken}');
|
||||
final tokenOIDC = authorizationTokenResponse.toTokenOIDC();
|
||||
if (tokenOIDC.isTokenValid()) {
|
||||
return tokenOIDC;
|
||||
} else {
|
||||
throw NotFoundAccessTokenException();
|
||||
throw AccessTokenInvalidException();
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<bool> logoutOidc(TokenId tokenId, OIDCConfiguration config, OIDCDiscoveryResponse oidcRescovery) async {
|
||||
final authorizationServiceConfiguration = oidcRescovery.authorizationEndpoint == null || oidcRescovery.tokenEndpoint == null
|
||||
? null
|
||||
: AuthorizationServiceConfiguration(
|
||||
authorizationEndpoint: oidcRescovery.authorizationEndpoint!,
|
||||
tokenEndpoint: oidcRescovery.tokenEndpoint!,
|
||||
endSessionEndpoint: oidcRescovery.endSessionEndpoint);
|
||||
|
||||
final endSession = await _appAuth.endSession(EndSessionRequest(
|
||||
idTokenHint: tokenId.uuid,
|
||||
postLogoutRedirectUrl: config.logoutRedirectUrl,
|
||||
discoveryUrl: config.discoveryUrl,
|
||||
serviceConfiguration: authorizationServiceConfiguration,
|
||||
preferEphemeralSession: true,
|
||||
));
|
||||
log('AuthenticationClientMobile::logoutOidc(): ${endSession?.state}');
|
||||
return endSession?.state?.isNotEmpty == true;
|
||||
Future<bool> logoutOidc(
|
||||
TokenId tokenId,
|
||||
OIDCConfiguration config,
|
||||
OIDCDiscoveryResponse discoveryResponse,
|
||||
) async {
|
||||
final endSessionRequest = getEndSessionRequest(
|
||||
tokenId,
|
||||
config,
|
||||
discoveryResponse,
|
||||
);
|
||||
final endSession = await _appAuth.endSession(endSessionRequest);
|
||||
log('$runtimeType::logoutOidc(): ${endSession.state}');
|
||||
return endSession.state?.isNotEmpty == true;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<TokenOIDC> refreshingTokensOIDC(String clientId, String redirectUrl,
|
||||
String discoveryUrl, List<String> scopes, String refreshToken) async {
|
||||
final tokenResponse = await _appAuth.token(TokenRequest(
|
||||
Future<TokenOIDC> refreshingTokensOIDC(
|
||||
String clientId,
|
||||
String redirectUrl,
|
||||
String discoveryUrl,
|
||||
List<String> scopes,
|
||||
String refreshToken,
|
||||
) async {
|
||||
try {
|
||||
final tokenRequest = getRefreshTokenRequest(
|
||||
clientId,
|
||||
redirectUrl,
|
||||
discoveryUrl: discoveryUrl,
|
||||
refreshToken: refreshToken,
|
||||
scopes: scopes));
|
||||
|
||||
log('AuthenticationClientMobile::refreshingTokensOIDC(): refreshToken: ${tokenResponse?.accessToken}');
|
||||
|
||||
if (tokenResponse != null) {
|
||||
final tokenOIDC = tokenResponse.toTokenOIDC(maybeAvailableRefreshToken: refreshToken);
|
||||
discoveryUrl,
|
||||
refreshToken,
|
||||
scopes,
|
||||
);
|
||||
final tokenResponse = await _appAuth.token(tokenRequest);
|
||||
log('$runtimeType::refreshingTokensOIDC():Token: ${tokenResponse.accessToken}');
|
||||
final tokenOIDC = tokenResponse.toTokenOIDC(
|
||||
maybeAvailableRefreshToken: refreshToken,
|
||||
);
|
||||
if (tokenOIDC.isTokenValid()) {
|
||||
return tokenOIDC;
|
||||
} else {
|
||||
throw AccessTokenInvalidException();
|
||||
}
|
||||
} else {
|
||||
throw NotFoundAccessTokenException();
|
||||
} catch (e) {
|
||||
logError('$runtimeType::refreshingTokensOIDC(): $e');
|
||||
throw handleException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -104,7 +110,7 @@ class AuthenticationClientMobile implements AuthenticationClientBase {
|
||||
intentFlags: ephemeralIntentFlags,
|
||||
),
|
||||
);
|
||||
log('AuthenticationClientMobile::signInTwakeWorkplace():Uri = $uri');
|
||||
log('$runtimeType::signInTwakeWorkplace():Uri = $uri');
|
||||
return TokenOIDC.fromUri(uri);
|
||||
}
|
||||
|
||||
@@ -117,7 +123,7 @@ class AuthenticationClientMobile implements AuthenticationClientBase {
|
||||
intentFlags: ephemeralIntentFlags,
|
||||
),
|
||||
);
|
||||
log('AuthenticationClientMobile::signUpTwakeWorkplace():Uri = $uri');
|
||||
log('$runtimeType::signUpTwakeWorkplace():Uri = $uri');
|
||||
return TokenOIDC.fromUri(uri);
|
||||
}
|
||||
}
|
||||
|
||||
+85
-70
@@ -9,89 +9,104 @@ import 'package:model/oidc/token_oidc.dart';
|
||||
import 'package:tmail_ui_user/features/login/data/extensions/authentication_token_extension.dart';
|
||||
import 'package:tmail_ui_user/features/login/data/extensions/token_response_extension.dart';
|
||||
import 'package:tmail_ui_user/features/login/data/network/authentication_client/authentication_client_base.dart';
|
||||
import 'package:tmail_ui_user/features/login/data/network/authentication_client/authentication_client_interaction_mixin.dart';
|
||||
import 'package:tmail_ui_user/features/login/data/utils/library_platform/app_auth_plugin/app_auth_plugin.dart';
|
||||
import 'package:tmail_ui_user/features/login/domain/exceptions/authentication_exception.dart';
|
||||
import 'package:tmail_ui_user/features/login/domain/extensions/oidc_configuration_extensions.dart';
|
||||
|
||||
class AuthenticationClientWeb implements AuthenticationClientBase {
|
||||
class AuthenticationClientWeb with AuthenticationClientInteractionMixin
|
||||
implements AuthenticationClientBase {
|
||||
|
||||
final AppAuthWebPlugin _appAuthWeb;
|
||||
|
||||
AuthenticationClientWeb(this._appAuthWeb);
|
||||
|
||||
@override
|
||||
Future<TokenOIDC> getTokenOIDC(String clientId, String redirectUrl,
|
||||
String discoveryUrl, List<String> scopes) async {
|
||||
final authorizationTokenResponse = await _appAuthWeb.authorizeAndExchangeCode(AuthorizationTokenRequest(
|
||||
Future<TokenOIDC> getTokenOIDC(
|
||||
String clientId,
|
||||
String redirectUrl,
|
||||
String discoveryUrl,
|
||||
List<String> scopes,
|
||||
) async {
|
||||
final authorizationTokenRequest = getAuthorizationTokenRequest(
|
||||
clientId,
|
||||
redirectUrl,
|
||||
discoveryUrl,
|
||||
scopes,
|
||||
);
|
||||
final authorizationTokenResponse = await _appAuthWeb.authorizeAndExchangeCode(
|
||||
authorizationTokenRequest,
|
||||
);
|
||||
log('$runtimeType::getTokenOIDC():Token: ${authorizationTokenResponse.accessToken}');
|
||||
final tokenOIDC = authorizationTokenResponse.toTokenOIDC();
|
||||
if (tokenOIDC.isTokenValid()) {
|
||||
return tokenOIDC;
|
||||
} else {
|
||||
throw AccessTokenInvalidException();
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<bool> logoutOidc(
|
||||
TokenId tokenId,
|
||||
OIDCConfiguration config,
|
||||
OIDCDiscoveryResponse discoveryResponse,
|
||||
) async {
|
||||
final endSessionRequest = getEndSessionRequest(
|
||||
tokenId,
|
||||
config,
|
||||
discoveryResponse,
|
||||
);
|
||||
final endSession = await _appAuthWeb.endSession(endSessionRequest);
|
||||
log('$runtimeType::logoutOidc(): ${endSession.state}');
|
||||
return endSession.state?.isNotEmpty == true;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<TokenOIDC> refreshingTokensOIDC(
|
||||
String clientId,
|
||||
String redirectUrl,
|
||||
String discoveryUrl,
|
||||
List<String> scopes,
|
||||
String refreshToken,
|
||||
) async {
|
||||
try {
|
||||
final tokenRequest = getRefreshTokenRequest(
|
||||
clientId,
|
||||
redirectUrl,
|
||||
discoveryUrl,
|
||||
refreshToken,
|
||||
scopes,
|
||||
);
|
||||
final tokenResponse = await _appAuthWeb.token(tokenRequest);
|
||||
final tokenOIDC = tokenResponse.toTokenOIDC(
|
||||
maybeAvailableRefreshToken: refreshToken,
|
||||
);
|
||||
if (tokenOIDC.isTokenValid()) {
|
||||
return tokenOIDC;
|
||||
} else {
|
||||
throw AccessTokenInvalidException();
|
||||
}
|
||||
} catch (e) {
|
||||
logError('$runtimeType::refreshingTokensOIDC(): $e');
|
||||
throw handleException(e);
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> authenticateOidcOnBrowser(
|
||||
String clientId,
|
||||
String redirectUrl,
|
||||
String discoveryUrl,
|
||||
List<String> scopes,
|
||||
) async {
|
||||
await _appAuthWeb.authorizeAndExchangeCode(
|
||||
AuthorizationTokenRequest(
|
||||
clientId,
|
||||
redirectUrl,
|
||||
discoveryUrl: discoveryUrl,
|
||||
scopes: scopes,
|
||||
preferEphemeralSession: true));
|
||||
|
||||
log('AuthClientMobile::getTokenOIDC(): token: ${authorizationTokenResponse?.accessToken}');
|
||||
|
||||
if (authorizationTokenResponse != null) {
|
||||
final tokenOIDC = authorizationTokenResponse.toTokenOIDC();
|
||||
if (tokenOIDC.isTokenValid()) {
|
||||
return tokenOIDC;
|
||||
} else {
|
||||
throw AccessTokenInvalidException();
|
||||
}
|
||||
} else {
|
||||
throw NotFoundAccessTokenException();
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<bool> logoutOidc(TokenId tokenId, OIDCConfiguration config, OIDCDiscoveryResponse oidcRescovery) async {
|
||||
final authorizationServiceConfiguration = oidcRescovery.authorizationEndpoint == null || oidcRescovery.tokenEndpoint == null
|
||||
? null
|
||||
: AuthorizationServiceConfiguration(
|
||||
authorizationEndpoint: oidcRescovery.authorizationEndpoint!,
|
||||
tokenEndpoint: oidcRescovery.tokenEndpoint!,
|
||||
endSessionEndpoint: oidcRescovery.endSessionEndpoint);
|
||||
final endSession = await _appAuthWeb.endSession(EndSessionRequest(
|
||||
idTokenHint: tokenId.uuid,
|
||||
postLogoutRedirectUrl: config.logoutRedirectUrl,
|
||||
discoveryUrl: config.discoveryUrl,
|
||||
serviceConfiguration: authorizationServiceConfiguration
|
||||
));
|
||||
return endSession != null;
|
||||
}
|
||||
|
||||
@override
|
||||
Future<TokenOIDC> refreshingTokensOIDC(String clientId, String redirectUrl,
|
||||
String discoveryUrl, List<String> scopes, String refreshToken) async {
|
||||
final tokenResponse = await _appAuthWeb.token(TokenRequest(
|
||||
clientId,
|
||||
redirectUrl,
|
||||
discoveryUrl: discoveryUrl,
|
||||
refreshToken: refreshToken,
|
||||
grantType: 'refresh_token',
|
||||
scopes: scopes));
|
||||
|
||||
if (tokenResponse != null) {
|
||||
final tokenOIDC = tokenResponse.toTokenOIDC(maybeAvailableRefreshToken: refreshToken);
|
||||
if (tokenOIDC.isTokenValid()) {
|
||||
return tokenOIDC;
|
||||
} else {
|
||||
throw AccessTokenInvalidException();
|
||||
}
|
||||
} else {
|
||||
throw NotFoundAccessTokenException();
|
||||
}
|
||||
}
|
||||
|
||||
@override
|
||||
Future<void> authenticateOidcOnBrowser(String clientId, String redirectUrl,
|
||||
String discoveryUrl, List<String> scopes) async {
|
||||
await _appAuthWeb.authorizeAndExchangeCode(
|
||||
AuthorizationTokenRequest(
|
||||
clientId,
|
||||
redirectUrl,
|
||||
discoveryUrl: discoveryUrl,
|
||||
scopes: scopes));
|
||||
),
|
||||
);
|
||||
}
|
||||
|
||||
@override
|
||||
|
||||
Reference in New Issue
Block a user