TF-2387 Handle signIn twake workplace via use company server

This commit is contained in:
dab246
2024-11-07 16:26:44 +07:00
committed by Dat H. Pham
parent 623826d29f
commit 31af783c52
10 changed files with 145 additions and 63 deletions
+1 -3
View File
@@ -6,6 +6,4 @@ APP_GRID_AVAILABLE=supported
FCM_AVAILABLE=supported
IOS_FCM=supported
FORWARD_WARNING_MESSAGE=
PLATFORM=other
REGISTRATION_URL=http://localhost/
SAAS_SERVER_URL=http://localhost/
PLATFORM=other
@@ -15,7 +15,7 @@ extension OidcConfigurationExtensions on OIDCConfiguration {
? AppConfig.domainRedirectUrl + loginRedirectOidcWeb
: '${AppConfig.domainRedirectUrl}/$loginRedirectOidcWeb';
} else {
return authority == AppConfig.saasRegistrationUrl
return _isSaasAuthority(authority)
? OIDCConstant.twakeWorkplaceRedirectUrl
: redirectOidcMobile;
}
@@ -29,11 +29,14 @@ extension OidcConfigurationExtensions on OIDCConfiguration {
return '${AppConfig.domainRedirectUrl}/$logoutRedirectOidcWeb';
}
} else {
return authority == AppConfig.saasRegistrationUrl
return _isSaasAuthority(authority)
? OIDCConstant.twakeWorkplaceRedirectUrl
: redirectOidcMobile;
}
}
}
bool _isSaasAuthority(String authority) =>
authority == AppConfig.saasRegistrationUrl;
String get signInTWPUrl => ServicePath(authority)
.withQueryParameters([
@@ -6,6 +6,7 @@ import 'package:tmail_ui_user/features/caching/clients/recent_login_username_cac
import 'package:tmail_ui_user/features/login/data/datasource/login_datasource.dart';
import 'package:tmail_ui_user/features/login/data/datasource_impl/hive_login_datasource_impl.dart';
import 'package:tmail_ui_user/features/login/data/datasource_impl/login_datasource_impl.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/dns_service.dart';
import 'package:tmail_ui_user/features/login/data/repository/login_repository_impl.dart';
import 'package:tmail_ui_user/features/login/domain/repository/account_repository.dart';
@@ -26,6 +27,11 @@ import 'package:tmail_ui_user/features/login/domain/usecases/get_token_oidc_inte
import 'package:tmail_ui_user/features/login/domain/usecases/save_login_url_on_mobile_interactor.dart';
import 'package:tmail_ui_user/features/login/domain/usecases/save_login_username_on_mobile_interactor.dart';
import 'package:tmail_ui_user/features/login/presentation/login_controller.dart';
import 'package:tmail_ui_user/features/starting_page/data/datasource/saas_authentication_datasource.dart';
import 'package:tmail_ui_user/features/starting_page/data/datasource_impl/saas_authentication_datasource_impl.dart';
import 'package:tmail_ui_user/features/starting_page/data/repository/saas_authentication_repository_impl.dart';
import 'package:tmail_ui_user/features/starting_page/domain/repository/saas_authentication_repository.dart';
import 'package:tmail_ui_user/features/starting_page/domain/usecase/sign_in_twake_workplace_interactor.dart';
import 'package:tmail_ui_user/main/exceptions/cache_exception_thrower.dart';
import 'package:tmail_ui_user/main/exceptions/remote_exception_thrower.dart';
@@ -47,12 +53,15 @@ class LoginBindings extends BaseBindings {
Get.find<SaveLoginUsernameOnMobileInteractor>(),
Get.find<GetAllRecentLoginUsernameOnMobileInteractor>(),
Get.find<DNSLookupToGetJmapUrlInteractor>(),
Get.find<SignInTwakeWorkplaceInteractor>(),
));
}
@override
void bindingsDataSource() {
Get.lazyPut<LoginDataSource>(() => Get.find<LoginDataSourceImpl>());
Get.lazyPut<SaasAuthenticationDataSource>(
() => Get.find<SaasAuthenticationDataSourceImpl>());
}
@override
@@ -66,6 +75,10 @@ class LoginBindings extends BaseBindings {
Get.find<DNSService>(),
Get.find<RemoteExceptionThrower>(),
));
Get.lazyPut(() => SaasAuthenticationDataSourceImpl(
Get.find<AuthenticationClientBase>(),
Get.find<RemoteExceptionThrower>(),
));
}
@override
@@ -98,11 +111,19 @@ class LoginBindings extends BaseBindings {
Get.lazyPut(() => SaveLoginUsernameOnMobileInteractor(Get.find<LoginRepository>(),));
Get.lazyPut(() => GetAllRecentLoginUsernameOnMobileInteractor(Get.find<LoginRepository>()));
Get.lazyPut(() => DNSLookupToGetJmapUrlInteractor(Get.find<LoginRepository>()));
Get.lazyPut(() => SignInTwakeWorkplaceInteractor(
Get.find<SaasAuthenticationRepository>(),
Get.find<AuthenticationOIDCRepository>(),
Get.find<AccountRepository>(),
Get.find<CredentialRepository>(),
));
}
@override
void bindingsRepository() {
Get.lazyPut<LoginRepository>(() => Get.find<LoginRepositoryImpl>());
Get.lazyPut<SaasAuthenticationRepository>(
() => Get.find<SaasAuthenticationRepositoryImpl>());
}
@override
@@ -113,5 +134,7 @@ class LoginBindings extends BaseBindings {
DataSourceType.network: Get.find<LoginDataSource>(),
}
));
Get.lazyPut(() => SaasAuthenticationRepositoryImpl(
Get.find<SaasAuthenticationDataSource>()));
}
}
@@ -14,6 +14,7 @@ import 'package:model/account/password.dart';
import 'package:model/oidc/oidc_configuration.dart';
import 'package:model/oidc/request/oidc_request.dart';
import 'package:model/oidc/response/oidc_response.dart';
import 'package:model/oidc/token_oidc.dart';
import 'package:tmail_ui_user/features/base/reloadable/reloadable_controller.dart';
import 'package:tmail_ui_user/features/home/domain/state/get_session_state.dart';
import 'package:tmail_ui_user/features/login/data/network/oidc_error.dart';
@@ -49,6 +50,8 @@ import 'package:tmail_ui_user/features/login/domain/usecases/save_login_url_on_m
import 'package:tmail_ui_user/features/login/domain/usecases/save_login_username_on_mobile_interactor.dart';
import 'package:tmail_ui_user/features/login/presentation/login_form_type.dart';
import 'package:tmail_ui_user/features/login/presentation/model/login_arguments.dart';
import 'package:tmail_ui_user/features/starting_page/domain/state/sign_in_twake_workplace_state.dart';
import 'package:tmail_ui_user/features/starting_page/domain/usecase/sign_in_twake_workplace_interactor.dart';
import 'package:tmail_ui_user/main/routes/app_routes.dart';
import 'package:tmail_ui_user/main/routes/route_navigation.dart';
import 'package:tmail_ui_user/main/routes/route_utils.dart';
@@ -70,6 +73,7 @@ class LoginController extends ReloadableController {
final SaveLoginUsernameOnMobileInteractor _saveLoginUsernameOnMobileInteractor;
final GetAllRecentLoginUsernameOnMobileInteractor _getAllRecentLoginUsernameOnMobileInteractor;
final DNSLookupToGetJmapUrlInteractor _dnsLookupToGetJmapUrlInteractor;
final SignInTwakeWorkplaceInteractor _signInTwakeWorkplaceInteractor;
final TextEditingController urlInputController = TextEditingController();
final TextEditingController usernameInputController = TextEditingController();
@@ -99,6 +103,7 @@ class LoginController extends ReloadableController {
this._saveLoginUsernameOnMobileInteractor,
this._getAllRecentLoginUsernameOnMobileInteractor,
this._dnsLookupToGetJmapUrlInteractor,
this._signInTwakeWorkplaceInteractor,
);
@override
@@ -130,7 +135,8 @@ class LoginController extends ReloadableController {
_handleCheckOIDCIsAvailableFailure(failure);
} else if (failure is GetStoredOidcConfigurationFailure ||
failure is GetOIDCIsAvailableFailure ||
failure is GetOIDCConfigurationFailure
failure is GetOIDCConfigurationFailure ||
failure is SignInTwakeWorkplaceFailure
) {
_handleCommonOIDCFailure();
} else if (failure is GetTokenOIDCFailure) {
@@ -168,6 +174,12 @@ class LoginController extends ReloadableController {
_loginSuccessAction(success);
} else if (success is DNSLookupToGetJmapUrlSuccess) {
_handleDNSLookupToGetJmapUrlSuccess(success);
} else if (success is SignInTwakeWorkplaceSuccess) {
_synchronizeTokenAndGetSession(
baseUri: success.baseUri,
tokenOIDC: success.tokenOIDC,
oidcConfiguration: success.oidcConfiguration,
);
} else {
super.handleSuccessViewState(success);
}
@@ -180,7 +192,9 @@ class LoginController extends ReloadableController {
_handleCheckOIDCIsAvailableFailure(failure);
} else if (failure is GetStoredOidcConfigurationFailure ||
failure is GetOIDCConfigurationFailure ||
failure is GetOIDCIsAvailableFailure) {
failure is GetOIDCIsAvailableFailure ||
failure is SignInTwakeWorkplaceFailure
) {
_handleCommonOIDCFailure();
} else if (failure is GetTokenOIDCFailure) {
_handleNoSuitableBrowserOIDC(failure)
@@ -326,11 +340,41 @@ class LoginController extends ReloadableController {
void _getOIDCConfigurationSuccess(GetOIDCConfigurationSuccess success) {
if (PlatformInfo.isWeb) {
_authenticateOidcOnBrowserAction(success.oidcConfiguration);
} else if (success.oidcConfiguration.authority == AppConfig.saasRegistrationUrl) {
_getTokenOIDCOnSaaSPlatform(success.oidcConfiguration);
} else {
_getTokenOIDCAction(success.oidcConfiguration);
}
}
void _getTokenOIDCOnSaaSPlatform(OIDCConfiguration oidcConfiguration) {
if (_currentBaseUrl != null) {
consumeState(_signInTwakeWorkplaceInteractor.execute(
baseUri: _currentBaseUrl!,
oidcConfiguration: oidcConfiguration,
));
} else {
dispatchState(Left(GetTokenOIDCFailure(CanNotFoundBaseUrl())));
}
}
void _synchronizeTokenAndGetSession({
required Uri baseUri,
required TokenOIDC tokenOIDC,
required OIDCConfiguration oidcConfiguration,
}) {
dynamicUrlInterceptors.setJmapUrl(baseUri.toString());
dynamicUrlInterceptors.changeBaseUrl(baseUri.toString());
authorizationInterceptors.setTokenAndAuthorityOidc(
newToken: tokenOIDC,
newConfig: oidcConfiguration);
authorizationIsolateInterceptors.setTokenAndAuthorityOidc(
newToken: tokenOIDC,
newConfig: oidcConfiguration);
getSessionAction();
}
void _getTokenOIDCAction(OIDCConfiguration config) {
if (_currentBaseUrl != null) {
consumeState(_getTokenOIDCInteractor.execute(_currentBaseUrl!, config));
@@ -357,15 +401,11 @@ class LoginController extends ReloadableController {
}
void _getTokenOIDCSuccess(GetTokenOIDCSuccess success) {
dynamicUrlInterceptors.setJmapUrl(_currentBaseUrl?.toString());
dynamicUrlInterceptors.changeBaseUrl(_currentBaseUrl?.toString());
authorizationInterceptors.setTokenAndAuthorityOidc(
newToken: success.tokenOIDC,
newConfig: success.configuration);
authorizationIsolateInterceptors.setTokenAndAuthorityOidc(
newToken: success.tokenOIDC,
newConfig: success.configuration);
getSessionAction();
_synchronizeTokenAndGetSession(
baseUri: _currentBaseUrl!,
tokenOIDC: success.tokenOIDC,
oidcConfiguration: success.configuration,
);
}
void _loginSuccessAction(AuthenticationUserSuccess success) {
@@ -46,11 +46,6 @@ class TwakeWelcomeController extends ReloadableController {
void onClickSignIn(BuildContext context) {
TipDialogHelper.loading(AppLocalizations.of(context).loadingPleaseWait);
if (AppConfig.saasJmapServerUrl.isEmpty) {
consumeState(Stream.value(Left(SignInTwakeWorkplaceFailure(CanNotFoundSaasServerUrl()))));
return;
}
final baseUri = Uri.tryParse(AppConfig.saasJmapServerUrl);
if (baseUri == null) {
@@ -71,11 +66,6 @@ class TwakeWelcomeController extends ReloadableController {
void onSignUpTwakeWorkplace(BuildContext context) {
TipDialogHelper.loading(AppLocalizations.of(context).loadingPleaseWait);
if (AppConfig.saasJmapServerUrl.isEmpty) {
consumeState(Stream.value(Left(SignUpTwakeWorkplaceFailure(CanNotFoundSaasServerUrl()))));
return;
}
final baseUri = Uri.tryParse(AppConfig.saasJmapServerUrl);
if (baseUri == null) {
+2 -4
View File
@@ -16,6 +16,8 @@ class AppConfig {
static const String iOSKeychainSharingService = 'com.linagora.ios.teammail.sessions';
static const String saasPlatform = 'saas';
static const String linagoraPrivacyUrl = 'https://www.linagora.com/en/legal/privacy';
static const String saasRegistrationUrl = 'https://sign-up.stg.lin-saas.com';
static const String saasJmapServerUrl = 'https://jmap.stg.lin-saas.com';
static String get baseUrl => dotenv.get('SERVER_URL', fallback: '');
static String get domainRedirectUrl => dotenv.get('DOMAIN_REDIRECT_URL', fallback: '');
@@ -66,8 +68,4 @@ class AppConfig {
static String get _platformEnv => dotenv.get('PLATFORM', fallback: 'other');
static bool get isSaasPlatForm => _platformEnv.toLowerCase() == saasPlatform;
static String get saasRegistrationUrl => dotenv.get('SAAS_REGISTRATION_URL', fallback: '');
static String get saasJmapServerUrl => dotenv.get('SAAS_JMAP_SERVER_URL', fallback: '');
}
+7 -14
View File
@@ -37,26 +37,19 @@ class TokenOIDC with EquatableMixin {
throw AccessTokenIsNullException();
}
final refreshToken = queryParams['refresh_token'];
if (refreshToken == null || refreshToken.isEmpty) {
throw RefreshTokenIsNullException();
}
final idToken = queryParams['id_token'];
if (idToken == null || idToken.isEmpty) {
throw TokenIdIsNullException();
}
final refreshToken = queryParams['refresh_token'] ?? '';
final idToken = queryParams['id_token'] ?? '';
final expiresIn = queryParams['expires_in'];
if (expiresIn == null || expiresIn.isEmpty) {
throw ExpiresTimeIsNullException();
}
final expiredTime = expiresIn == null
? null
: DateTime.now().add(Duration(seconds: int.parse(expiresIn)));
return TokenOIDC(
accessToken,
TokenId(idToken),
refreshToken,
expiredTime: DateTime.now().add(Duration(seconds: int.parse(expiresIn))),
expiredTime: expiredTime,
);
}
-18
View File
@@ -20,24 +20,6 @@ void main() {
expect(() => TokenOIDC.fromUri(uriString), throwsA(isA<AccessTokenIsNullException>()));
});
test('throws RefreshTokenIsNullException if refresh_token is missing', () {
const uriString = 'https://example.com/callback?access_token=valid_access&id_token=valid_id&expires_in=3600';
expect(() => TokenOIDC.fromUri(uriString), throwsA(isA<RefreshTokenIsNullException>()));
});
test('throws TokenIdIsNullException if id_token is missing', () {
const uriString = 'https://example.com/callback?access_token=valid_access&refresh_token=valid_refresh&expires_in=3600';
expect(() => TokenOIDC.fromUri(uriString), throwsA(isA<TokenIdIsNullException>()));
});
test('throws ExpiresTimeIsNullException if expires_in is missing', () {
const uriString = 'https://example.com/callback?access_token=valid_access&refresh_token=valid_refresh&id_token=valid_id';
expect(() => TokenOIDC.fromUri(uriString), throwsA(isA<ExpiresTimeIsNullException>()));
});
test('throws FormatException on invalid URL format', () {
const uriString = '::Not valid URI::';
@@ -0,0 +1,50 @@
import 'package:core/data/model/query/query_parameter.dart';
import 'package:core/data/network/config/service_path.dart';
import 'package:flutter_test/flutter_test.dart';
import 'package:tmail_ui_user/features/login/data/extensions/service_path_extension.dart';
import 'package:tmail_ui_user/features/login/data/network/config/oidc_constant.dart';
void main() {
test('signInTWPUrl should return the correct URL with query parameters', () {
// Arrange
const authority = 'https://authority.example.com';
const expectedUrl = '$authority?${OIDCConstant.postLoginRedirectUrlPathParams}=${OIDCConstant.twakeWorkplaceRedirectUrl}&app=${OIDCConstant.appParameter}';
final service = ServicePath(authority);
// Act
final actualUrl = service
.withQueryParameters([
StringQueryParameter(
OIDCConstant.postLoginRedirectUrlPathParams,
OIDCConstant.twakeWorkplaceRedirectUrl,
),
StringQueryParameter('app', OIDCConstant.appParameter),
])
.generateEndpointPath();
// Assert
expect(actualUrl, equals(expectedUrl));
});
test('signUpTWPUrl should return the correct URL with query parameters', () {
// Arrange
const authority = 'https://authority.example.com';
const expectedUrl = '$authority?${OIDCConstant.postRegisteredRedirectUrlPathParams}=${OIDCConstant.twakeWorkplaceRedirectUrl}&app=${OIDCConstant.appParameter}';
final service = ServicePath(authority);
// Act
final actualUrl = service
.withQueryParameters([
StringQueryParameter(
OIDCConstant.postRegisteredRedirectUrlPathParams,
OIDCConstant.twakeWorkplaceRedirectUrl,
),
StringQueryParameter('app', OIDCConstant.appParameter),
])
.generateEndpointPath();
// Assert
expect(actualUrl, equals(expectedUrl));
});
}
@@ -33,6 +33,7 @@ import 'package:tmail_ui_user/features/login/presentation/login_controller.dart'
import 'package:tmail_ui_user/features/login/presentation/login_form_type.dart';
import 'package:tmail_ui_user/features/manage_account/data/local/language_cache_manager.dart';
import 'package:tmail_ui_user/features/manage_account/domain/usecases/log_out_oidc_interactor.dart';
import 'package:tmail_ui_user/features/starting_page/domain/usecase/sign_in_twake_workplace_interactor.dart';
import 'package:tmail_ui_user/main/bindings/network/binding_tag.dart';
import 'package:tmail_ui_user/main/utils/toast_manager.dart';
import 'package:uuid/uuid.dart';
@@ -62,6 +63,7 @@ import 'login_controller_test.mocks.dart';
MockSpec<SaveLoginUsernameOnMobileInteractor>(),
MockSpec<GetAllRecentLoginUsernameOnMobileInteractor>(),
MockSpec<DNSLookupToGetJmapUrlInteractor>(),
MockSpec<SignInTwakeWorkplaceInteractor>(),
MockSpec<GetSessionInteractor>(),
MockSpec<GetAuthenticatedAccountInteractor>(),
MockSpec<UpdateAccountCacheInteractor>(),
@@ -84,6 +86,7 @@ void main() {
late MockSaveLoginUsernameOnMobileInteractor mockSaveLoginUsernameOnMobileInteractor;
late MockGetAllRecentLoginUsernameOnMobileInteractor mockGetAllRecentLoginUsernameOnMobileInteractor;
late MockDNSLookupToGetJmapUrlInteractor mockDNSLookupToGetJmapUrlInteractor;
late MockSignInTwakeWorkplaceInteractor mockSignInTwakeWorkplaceInteractor;
late MockGetSessionInteractor mockGetSessionInteractor;
late MockGetAuthenticatedAccountInteractor mockGetAuthenticatedAccountInteractor;
late MockUpdateAccountCacheInteractor mockUpdateAccountCacheInteractor;
@@ -118,6 +121,7 @@ void main() {
mockSaveLoginUsernameOnMobileInteractor = MockSaveLoginUsernameOnMobileInteractor();
mockGetAllRecentLoginUsernameOnMobileInteractor = MockGetAllRecentLoginUsernameOnMobileInteractor();
mockDNSLookupToGetJmapUrlInteractor = MockDNSLookupToGetJmapUrlInteractor();
mockSignInTwakeWorkplaceInteractor = MockSignInTwakeWorkplaceInteractor();
// mock reloadable controller
mockGetSessionInteractor = MockGetSessionInteractor();
@@ -175,6 +179,7 @@ void main() {
mockSaveLoginUsernameOnMobileInteractor,
mockGetAllRecentLoginUsernameOnMobileInteractor,
mockDNSLookupToGetJmapUrlInteractor,
mockSignInTwakeWorkplaceInteractor,
);