diff --git a/lib/features/login/data/datasource/authentication_oidc_datasource.dart b/lib/features/login/data/datasource/authentication_oidc_datasource.dart index 53e91f8bc..9ffa730a0 100644 --- a/lib/features/login/data/datasource/authentication_oidc_datasource.dart +++ b/lib/features/login/data/datasource/authentication_oidc_datasource.dart @@ -2,9 +2,9 @@ import 'package:model/model.dart'; abstract class AuthenticationOIDCDataSource { - Future checkOIDCIsAvailable(OIDCRequest oidcRequest); + Future checkOIDCIsAvailable(OIDCRequest oidcRequest); - Future getOIDCConfiguration(Uri baseUri); + Future getOIDCConfiguration(Uri baseUri, OIDCResponse oidcResponse); Future getTokenOIDC(String clientId, String redirectUrl, String discoveryUrl, List scopes); } \ No newline at end of file diff --git a/lib/features/login/data/datasource_impl/authentication_oidc_datasource_impl.dart b/lib/features/login/data/datasource_impl/authentication_oidc_datasource_impl.dart index 3a22cca86..0066af245 100644 --- a/lib/features/login/data/datasource_impl/authentication_oidc_datasource_impl.dart +++ b/lib/features/login/data/datasource_impl/authentication_oidc_datasource_impl.dart @@ -9,19 +9,19 @@ class AuthenticationOIDCDataSourceImpl extends AuthenticationOIDCDataSource { AuthenticationOIDCDataSourceImpl(this._oidcHttpClient); @override - Future checkOIDCIsAvailable(OIDCRequest oidcRequest) { + Future checkOIDCIsAvailable(OIDCRequest oidcRequest) { return Future.sync(() async { final oidcResponse = await _oidcHttpClient.checkOIDCIsAvailable(oidcRequest); - return oidcResponse != null; + return oidcResponse!; }).catchError((error) { throw error; }); } @override - Future getOIDCConfiguration(Uri baseUri) { + Future getOIDCConfiguration(Uri baseUri, OIDCResponse oidcResponse) { return Future.sync(() async { - return await _oidcHttpClient.getOIDCConfiguration(baseUri); + return await _oidcHttpClient.getOIDCConfiguration(baseUri, oidcResponse); }).catchError((error) { throw error; }); diff --git a/lib/features/login/data/network/config/oidc_constant.dart b/lib/features/login/data/network/config/oidc_constant.dart new file mode 100644 index 000000000..3c1cfb3fe --- /dev/null +++ b/lib/features/login/data/network/config/oidc_constant.dart @@ -0,0 +1,4 @@ +class OIDCConstant { + static String get mobileOidcClientId => 'teammail-mobile'; + static List get oidcScope => ['openid', 'offline_access']; +} \ No newline at end of file diff --git a/lib/features/login/data/network/oidc_error.dart b/lib/features/login/data/network/oidc_error.dart new file mode 100644 index 000000000..7e4464081 --- /dev/null +++ b/lib/features/login/data/network/oidc_error.dart @@ -0,0 +1 @@ +class CanNotFoundOIDCAuthority implements Exception {} \ No newline at end of file diff --git a/lib/features/login/data/network/oidc_http_client.dart b/lib/features/login/data/network/oidc_http_client.dart index ad335827b..e8cb10032 100644 --- a/lib/features/login/data/network/oidc_http_client.dart +++ b/lib/features/login/data/network/oidc_http_client.dart @@ -5,8 +5,10 @@ import 'package:core/core.dart'; import 'package:flutter_appauth/flutter_appauth.dart'; import 'package:model/model.dart'; import 'package:tmail_ui_user/features/login/data/extensions/authentication_token_extension.dart'; -import 'package:tmail_ui_user/features/login/data/network/endpoint.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'; +import 'package:tmail_ui_user/features/login/data/network/endpoint.dart'; +import 'package:tmail_ui_user/features/login/data/network/oidc_error.dart'; class OIDCHttpClient { @@ -21,7 +23,7 @@ class OIDCHttpClient { .generateOIDCPath(Uri.parse(oidcRequest.baseUrl)) .withQueryParameters([ StringQueryParameter('resource', oidcRequest.resourceUrl), - StringQueryParameter('rel', oidcRequest.relUrl), + StringQueryParameter('rel', OIDCRequest.relUrl), ]) .generateEndpointPath() ); @@ -33,8 +35,16 @@ class OIDCHttpClient { } } - Future getOIDCConfiguration(Uri baseUri) async { - return OIDCConfiguration.initial(); + Future getOIDCConfiguration(Uri baseUri, OIDCResponse oidcResponse) async { + if (oidcResponse.links.isEmpty) { + throw CanNotFoundOIDCAuthority(); + } + log('OIDCHttpClient::getOIDCConfiguration(): href: ${oidcResponse.links[0].href}'); + return OIDCConfiguration( + authority: oidcResponse.links[0].href.toString(), + clientId: OIDCConstant.mobileOidcClientId, + scopes: OIDCConstant.oidcScope + ); } Future getTokenOIDC(String clientId, String redirectUrl, String discoveryUrl, List scopes) async { diff --git a/lib/features/login/data/repository/authentication_oidc_repository_impl.dart b/lib/features/login/data/repository/authentication_oidc_repository_impl.dart index e4de26706..6a1eb622a 100644 --- a/lib/features/login/data/repository/authentication_oidc_repository_impl.dart +++ b/lib/features/login/data/repository/authentication_oidc_repository_impl.dart @@ -1,5 +1,6 @@ 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/login/data/datasource/authentication_oidc_datasource.dart'; import 'package:tmail_ui_user/features/login/domain/repository/authentication_oidc_repository.dart'; @@ -10,13 +11,13 @@ class AuthenticationOIDCRepositoryImpl extends AuthenticationOIDCRepository { AuthenticationOIDCRepositoryImpl(this._oidcDataSource); @override - Future checkOIDCIsAvailable(OIDCRequest oidcRequest) { + Future checkOIDCIsAvailable(OIDCRequest oidcRequest) { return _oidcDataSource.checkOIDCIsAvailable(oidcRequest); } @override - Future getOIDCConfiguration(Uri baseUri) { - return _oidcDataSource.getOIDCConfiguration(baseUri); + Future getOIDCConfiguration(Uri baseUri, OIDCResponse oidcResponse) { + return _oidcDataSource.getOIDCConfiguration(baseUri, oidcResponse); } @override diff --git a/lib/features/login/domain/repository/authentication_oidc_repository.dart b/lib/features/login/domain/repository/authentication_oidc_repository.dart index 699076b6a..cda0bff6c 100644 --- a/lib/features/login/domain/repository/authentication_oidc_repository.dart +++ b/lib/features/login/domain/repository/authentication_oidc_repository.dart @@ -2,9 +2,9 @@ import 'package:model/model.dart'; abstract class AuthenticationOIDCRepository { - Future checkOIDCIsAvailable(OIDCRequest oidcRequest); + Future checkOIDCIsAvailable(OIDCRequest oidcRequest); - Future getOIDCConfiguration(Uri baseUri); + Future getOIDCConfiguration(Uri baseUri, OIDCResponse oidcResponse); Future getTokenOIDC(String clientId, String redirectUrl, String discoveryUrl, List scopes); } \ No newline at end of file diff --git a/lib/features/login/domain/state/check_oidc_is_available_state.dart b/lib/features/login/domain/state/check_oidc_is_available_state.dart index 3a26181b4..8d125634e 100644 --- a/lib/features/login/domain/state/check_oidc_is_available_state.dart +++ b/lib/features/login/domain/state/check_oidc_is_available_state.dart @@ -1,11 +1,13 @@ import 'package:core/core.dart'; +import 'package:model/oidc/response/oidc_response.dart'; class CheckOIDCIsAvailableSuccess extends UIState { + final OIDCResponse oidcResponse; - CheckOIDCIsAvailableSuccess(); + CheckOIDCIsAvailableSuccess(this.oidcResponse); @override - List get props => []; + List get props => [oidcResponse]; } class CheckOIDCIsAvailableFailure extends FeatureFailure { diff --git a/lib/features/login/domain/usecases/check_oidc_is_available_interactor.dart b/lib/features/login/domain/usecases/check_oidc_is_available_interactor.dart index f067ab944..29797f37e 100644 --- a/lib/features/login/domain/usecases/check_oidc_is_available_interactor.dart +++ b/lib/features/login/domain/usecases/check_oidc_is_available_interactor.dart @@ -13,9 +13,7 @@ class CheckOIDCIsAvailableInteractor { try { final result = await _oidcRepository.checkOIDCIsAvailable(oidcRequest); log('CheckOIDCIsAvailableInteractor::execute(): result: $result'); - return result - ? Right(CheckOIDCIsAvailableSuccess()) - : Left(CheckOIDCIsAvailableFailure(null)); + return Right(CheckOIDCIsAvailableSuccess(result)); } catch (e) { log('CheckOIDCIsAvailableInteractor::execute(): ERROR: $e'); return Left(CheckOIDCIsAvailableFailure(e)); diff --git a/lib/features/login/domain/usecases/get_oidc_configuration_interactor.dart b/lib/features/login/domain/usecases/get_oidc_configuration_interactor.dart index 3664eb684..12d1645bf 100644 --- a/lib/features/login/domain/usecases/get_oidc_configuration_interactor.dart +++ b/lib/features/login/domain/usecases/get_oidc_configuration_interactor.dart @@ -1,5 +1,6 @@ import 'package:core/core.dart'; import 'package:dartz/dartz.dart'; +import 'package:model/model.dart'; import 'package:tmail_ui_user/features/login/domain/repository/authentication_oidc_repository.dart'; import 'package:tmail_ui_user/features/login/domain/state/get_oidc_configuration_state.dart'; @@ -8,9 +9,9 @@ class GetOIDCConfigurationInteractor { GetOIDCConfigurationInteractor(this._oidcRepository); - Future> execute(Uri baseUri) async { + Future> execute(Uri baseUri, OIDCResponse oidcResponse) async { try { - final oidcConfiguration = await _oidcRepository.getOIDCConfiguration(baseUri); + final oidcConfiguration = await _oidcRepository.getOIDCConfiguration(baseUri, oidcResponse); log('GetOIDCConfigurationInteractor::execute(): oidcConfiguration: $oidcConfiguration'); return Right(GetOIDCConfigurationSuccess(oidcConfiguration)); } catch (e) { diff --git a/lib/features/login/presentation/login_controller.dart b/lib/features/login/presentation/login_controller.dart index 7cb967201..1ce11ac5c 100644 --- a/lib/features/login/presentation/login_controller.dart +++ b/lib/features/login/presentation/login_controller.dart @@ -45,6 +45,7 @@ class LoginController extends GetxController { String? _urlText; String? _userNameText; String? _passwordText; + OIDCResponse? _oidcResponse; void setUrlText(String url) => _urlText = url.trim().formatURLValid(); @@ -75,8 +76,9 @@ class LoginController extends GetxController { loginState.value = LoginState(Left(LoginMissUrlAction())); } else { loginState.value = LoginState(Right(LoginLoadingAction())); + log('LoginController::_checkOIDCIsAvailable(): origin: + ${baseUri.origin}'); await _checkOIDCIsAvailableInteractor - .execute(OIDCRequest(baseUrl: baseUri.origin)) + .execute(OIDCRequest(baseUrl: baseUri.origin, resourceUrl: baseUri.origin)) .then((response) => response.fold( (failure) => _showFormLoginWithCredentialAction(), (success) => success is CheckOIDCIsAvailableSuccess @@ -88,6 +90,7 @@ class LoginController extends GetxController { void _showFormLoginWithSSOAction(CheckOIDCIsAvailableSuccess success) { loginState.value = LoginState(Right(success)); loginFormType.value = LoginFormType.ssoForm; + _oidcResponse = success.oidcResponse; } void handleBackInCredentialForm() { @@ -126,22 +129,26 @@ class LoginController extends GetxController { void _getOIDCConfiguration(Uri baseUri) async { loginState.value = LoginState(Right(LoginLoadingAction())); - await _getOIDCConfigurationInteractor.execute(baseUri) + if (_oidcResponse != null) { + await _getOIDCConfigurationInteractor.execute(baseUri, _oidcResponse!) .then((response) => response.fold( - (failure) { - if (failure is GetOIDCConfigurationFailure) { - loginState.value = LoginState(Left(failure)); - } else { - loginState.value = LoginState(Left(LoginCanNotVerifySSOConfigurationAction())); - } - }, - (success) { - if (success is GetOIDCConfigurationSuccess) { - _getOIDCConfigurationSuccess(success); - } else { - loginState.value = LoginState(Left(LoginCanNotVerifySSOConfigurationAction())); - } - })); + (failure) { + if (failure is GetOIDCConfigurationFailure) { + loginState.value = LoginState(Left(failure)); + } else { + loginState.value = LoginState( + Left(LoginCanNotVerifySSOConfigurationAction())); + } + }, + (success) { + if (success is GetOIDCConfigurationSuccess) { + _getOIDCConfigurationSuccess(success); + } else { + loginState.value = LoginState( + Left(LoginCanNotVerifySSOConfigurationAction())); + } + })); + } } void _getOIDCConfigurationSuccess(GetOIDCConfigurationSuccess success) { diff --git a/lib/l10n/intl_messages.arb b/lib/l10n/intl_messages.arb index d28eb2e09..ecfeee5e6 100644 --- a/lib/l10n/intl_messages.arb +++ b/lib/l10n/intl_messages.arb @@ -1,5 +1,5 @@ { - "@@last_modified": "2022-05-26T12:07:26.941919", + "@@last_modified": "2022-05-27T10:11:30.148525", "initializing_data": "Initializing data...", "@initializing_data": { "type": "text", @@ -1353,5 +1353,23 @@ "type": "text", "placeholders_order": [], "placeholders": {} + }, + "loginInputSSOMessage": "Sign-in with my SSO account", + "@loginInputSSOMessage": { + "type": "text", + "placeholders_order": [], + "placeholders": {} + }, + "canNotVerifySSOConfiguration": "Can not verify SSO configuration, please check with your system administrator", + "@canNotVerifySSOConfiguration": { + "type": "text", + "placeholders_order": [], + "placeholders": {} + }, + "canNotGetToken": "Can not get token, please check with your system administrator", + "@canNotGetToken": { + "type": "text", + "placeholders_order": [], + "placeholders": {} } } \ No newline at end of file diff --git a/model/lib/oidc/oidc_configuration.dart b/model/lib/oidc/oidc_configuration.dart index 68bce8fbe..7824bc033 100644 --- a/model/lib/oidc/oidc_configuration.dart +++ b/model/lib/oidc/oidc_configuration.dart @@ -16,14 +16,6 @@ class OIDCConfiguration with EquatableMixin { required this.scopes }); - factory OIDCConfiguration.initial() { - return OIDCConfiguration( - authority: 'https://auth.upn.integration-open-paas.org/auth/realms/mobile', - clientId: 'teammail-mobile', - scopes: ['openid', 'offline_access'] - ); - } - String get discoveryUrl { if (authority.endsWith('/')) { return authority + wellKnownOpenId; diff --git a/model/lib/oidc/request/oidc_request.dart b/model/lib/oidc/request/oidc_request.dart index f47a1512a..3c8903e29 100644 --- a/model/lib/oidc/request/oidc_request.dart +++ b/model/lib/oidc/request/oidc_request.dart @@ -4,10 +4,10 @@ import 'package:equatable/equatable.dart'; class OIDCRequest with EquatableMixin { final String baseUrl; - final String resourceUrl = 'https://gateway.upn.integration-open-paas.org'; - final String relUrl = 'http://openid.net/specs/connect/1.0/issuer'; + final String resourceUrl; + static const String relUrl = 'http://openid.net/specs/connect/1.0/issuer'; - OIDCRequest({required this.baseUrl}); + OIDCRequest({required this.baseUrl, required this.resourceUrl}); @override List get props => [baseUrl, resourceUrl, relUrl];