TF-571 Check OIDC is available
This commit is contained in:
@@ -0,0 +1,6 @@
|
||||
|
||||
import 'package:model/model.dart';
|
||||
|
||||
abstract class AuthenticationOIDCDataSource {
|
||||
Future<bool> checkOIDCIsAvailable(OIDCRequest oidcRequest);
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
import 'package:model/model.dart';
|
||||
import 'package:tmail_ui_user/features/login/data/datasource/authentication_oidc_datasource.dart';
|
||||
import 'package:tmail_ui_user/features/login/data/network/oidc_http_client.dart';
|
||||
|
||||
class AuthenticationOIDCDataSourceImpl extends AuthenticationOIDCDataSource {
|
||||
|
||||
final OIDCHttpClient _oidcHttpClient;
|
||||
|
||||
AuthenticationOIDCDataSourceImpl(this._oidcHttpClient);
|
||||
|
||||
@override
|
||||
Future<bool> checkOIDCIsAvailable(OIDCRequest oidcRequest) {
|
||||
return Future.sync(() async {
|
||||
final oidcResponse = await _oidcHttpClient.checkOIDCIsAvailable(oidcRequest);
|
||||
return oidcResponse != null;
|
||||
}).catchError((error) {
|
||||
throw error;
|
||||
});
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
|
||||
import 'package:core/core.dart';
|
||||
import 'package:tmail_ui_user/features/login/data/network/endpoint.dart';
|
||||
|
||||
extension ServicePathExtension on ServicePath {
|
||||
String generateEndpointPath() {
|
||||
return path;
|
||||
}
|
||||
|
||||
ServicePath generateOIDCPath(Uri baseUrl) {
|
||||
return ServicePath(baseUrl.origin + Endpoint.oidc + path);
|
||||
}
|
||||
|
||||
ServicePath withQueryParameters(List<QueryParameter> queryParameters) {
|
||||
if (queryParameters.isEmpty) {
|
||||
return this;
|
||||
}
|
||||
return ServicePath('$path?${queryParameters
|
||||
.map((query) => '${query.queryName}=${query.queryValue}').join('&')}');
|
||||
}
|
||||
|
||||
ServicePath withPathParameter([String? pathParameter]) {
|
||||
if (pathParameter == null || pathParameter.isEmpty) {
|
||||
return this;
|
||||
}
|
||||
|
||||
return ServicePath('$path/$pathParameter');
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
import 'package:core/data/network/config/service_path.dart';
|
||||
|
||||
class Endpoint {
|
||||
static const String oidc = '/oidc';
|
||||
|
||||
static final ServicePath webFinger = ServicePath('/.well-known/webfinger');
|
||||
}
|
||||
@@ -0,0 +1,32 @@
|
||||
|
||||
import 'dart:convert';
|
||||
|
||||
import 'package:core/core.dart';
|
||||
import 'package:model/model.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';
|
||||
|
||||
class OIDCHttpClient {
|
||||
|
||||
final DioClient _dioClient;
|
||||
|
||||
OIDCHttpClient(this._dioClient);
|
||||
|
||||
Future<OIDCResponse?> checkOIDCIsAvailable(OIDCRequest oidcRequest) async {
|
||||
final result = await _dioClient.get(
|
||||
Endpoint.webFinger
|
||||
.generateOIDCPath(Uri.parse(oidcRequest.baseUrl))
|
||||
.withQueryParameters([
|
||||
StringQueryParameter('resource', oidcRequest.resourceUrl),
|
||||
StringQueryParameter('rel', oidcRequest.relUrl),
|
||||
])
|
||||
.generateEndpointPath()
|
||||
);
|
||||
log('OIDCHttpClient::checkOIDCIsAvailable(): RESULT: $result');
|
||||
if (result is Map<String, dynamic>) {
|
||||
return OIDCResponse.fromJson(result);
|
||||
} else {
|
||||
return OIDCResponse.fromJson(jsonDecode(result));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
import 'package:model/oidc/request/oidc_request.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';
|
||||
|
||||
class AuthenticationOIDCRepositoryImpl extends AuthenticationOIDCRepository {
|
||||
final AuthenticationOIDCDataSource _oidcDataSource;
|
||||
|
||||
AuthenticationOIDCRepositoryImpl(this._oidcDataSource);
|
||||
|
||||
@override
|
||||
Future<bool> checkOIDCIsAvailable(OIDCRequest oidcRequest) {
|
||||
return _oidcDataSource.checkOIDCIsAvailable(oidcRequest);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
|
||||
import 'package:model/oidc/request/oidc_request.dart';
|
||||
|
||||
abstract class AuthenticationOIDCRepository {
|
||||
Future<bool> checkOIDCIsAvailable(OIDCRequest oidcRequest);
|
||||
}
|
||||
@@ -0,0 +1,18 @@
|
||||
import 'package:core/core.dart';
|
||||
|
||||
class CheckOIDCIsAvailableSuccess extends UIState {
|
||||
|
||||
CheckOIDCIsAvailableSuccess();
|
||||
|
||||
@override
|
||||
List<Object> get props => [];
|
||||
}
|
||||
|
||||
class CheckOIDCIsAvailableFailure extends FeatureFailure {
|
||||
final dynamic exception;
|
||||
|
||||
CheckOIDCIsAvailableFailure(this.exception);
|
||||
|
||||
@override
|
||||
List<Object> get props => [exception];
|
||||
}
|
||||
@@ -0,0 +1,24 @@
|
||||
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/check_oidc_is_available_state.dart';
|
||||
|
||||
class CheckOIDCIsAvailableInteractor {
|
||||
final AuthenticationOIDCRepository _oidcRepository;
|
||||
|
||||
CheckOIDCIsAvailableInteractor(this._oidcRepository);
|
||||
|
||||
Future<Either<Failure, Success>> execute(OIDCRequest oidcRequest) async {
|
||||
try {
|
||||
final result = await _oidcRepository.checkOIDCIsAvailable(oidcRequest);
|
||||
log('CheckOIDCIsAvailableInteractor::execute(): result: $result');
|
||||
return result
|
||||
? Right<Failure, Success>(CheckOIDCIsAvailableSuccess())
|
||||
: Left<Failure, Success>(CheckOIDCIsAvailableFailure(null));
|
||||
} catch (e) {
|
||||
log('CheckOIDCIsAvailableInteractor::execute(): ERROR: $e');
|
||||
return Left<Failure, Success>(CheckOIDCIsAvailableFailure(e));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -42,6 +42,8 @@ abstract class BaseLoginView extends GetWidget<LoginController> {
|
||||
(success) {
|
||||
if (loginController.loginFormType.value == LoginFormType.credentialForm || kIsWeb) {
|
||||
return AppLocalizations.of(context).loginInputCredentialMessage;
|
||||
} else if (loginController.loginFormType.value == LoginFormType.ssoForm) {
|
||||
return AppLocalizations.of(context).loginInputSSOMessage;
|
||||
}
|
||||
return AppLocalizations.of(context).loginInputUrlMessage;
|
||||
}))
|
||||
|
||||
@@ -3,33 +3,48 @@ import 'package:get/get.dart';
|
||||
import 'package:shared_preferences/shared_preferences.dart';
|
||||
import 'package:tmail_ui_user/features/base/base_bindings.dart';
|
||||
import 'package:tmail_ui_user/features/login/data/datasource/authentication_datasource.dart';
|
||||
import 'package:tmail_ui_user/features/login/data/datasource/authentication_oidc_datasource.dart';
|
||||
import 'package:tmail_ui_user/features/login/data/datasource_impl/authentication_datasource_impl.dart';
|
||||
import 'package:tmail_ui_user/features/login/data/datasource_impl/authentication_oidc_datasource_impl.dart';
|
||||
import 'package:tmail_ui_user/features/login/data/network/oidc_http_client.dart';
|
||||
import 'package:tmail_ui_user/features/login/data/repository/authentication_oidc_repository_impl.dart';
|
||||
import 'package:tmail_ui_user/features/login/data/repository/authentication_repository_impl.dart';
|
||||
import 'package:tmail_ui_user/features/login/data/repository/credential_repository_impl.dart';
|
||||
import 'package:tmail_ui_user/features/login/domain/repository/authentication_oidc_repository.dart';
|
||||
import 'package:tmail_ui_user/features/login/domain/repository/authentication_repository.dart';
|
||||
import 'package:tmail_ui_user/features/login/domain/repository/credential_repository.dart';
|
||||
import 'package:tmail_ui_user/features/login/domain/usecases/authentication_user_interactor.dart';
|
||||
import 'package:tmail_ui_user/features/login/domain/usecases/check_oidc_is_available_interactor.dart';
|
||||
import 'package:tmail_ui_user/features/login/presentation/login_controller.dart';
|
||||
|
||||
class LoginBindings extends BaseBindings {
|
||||
|
||||
@override
|
||||
void dependencies() {
|
||||
Get.lazyPut(() => OIDCHttpClient(Get.find<DioClient>()));
|
||||
super.dependencies();
|
||||
}
|
||||
|
||||
@override
|
||||
void bindingsController() {
|
||||
Get.lazyPut(() => LoginController(
|
||||
Get.find<AuthenticationInteractor>(),
|
||||
Get.find<DynamicUrlInterceptors>(),
|
||||
Get.find<AuthorizationInterceptors>(),
|
||||
Get.find<CheckOIDCIsAvailableInteractor>(),
|
||||
));
|
||||
}
|
||||
|
||||
@override
|
||||
void bindingsDataSource() {
|
||||
Get.lazyPut<AuthenticationDataSource>(() => Get.find<AuthenticationDataSourceImpl>());
|
||||
Get.lazyPut<AuthenticationOIDCDataSource>(() => Get.find<AuthenticationOIDCDataSourceImpl>());
|
||||
}
|
||||
|
||||
@override
|
||||
void bindingsDataSourceImpl() {
|
||||
Get.lazyPut(() => AuthenticationDataSourceImpl());
|
||||
Get.lazyPut(() => AuthenticationOIDCDataSourceImpl(Get.find<OIDCHttpClient>()));
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -38,17 +53,22 @@ class LoginBindings extends BaseBindings {
|
||||
Get.find<AuthenticationRepository>(),
|
||||
Get.find<CredentialRepository>(),
|
||||
));
|
||||
Get.lazyPut(() => CheckOIDCIsAvailableInteractor(
|
||||
Get.find<AuthenticationOIDCRepository>(),
|
||||
));
|
||||
}
|
||||
|
||||
@override
|
||||
void bindingsRepository() {
|
||||
Get.lazyPut<CredentialRepository>(() => Get.find<CredentialRepositoryImpl>());
|
||||
Get.lazyPut<AuthenticationRepository>(() => Get.find<AuthenticationRepositoryImpl>());
|
||||
Get.lazyPut<AuthenticationOIDCRepository>(() => Get.find<AuthenticationOIDCRepositoryImpl>());
|
||||
}
|
||||
|
||||
@override
|
||||
void bindingsRepositoryImpl() {
|
||||
Get.lazyPut(() => CredentialRepositoryImpl(Get.find<SharedPreferences>()));
|
||||
Get.lazyPut(() => AuthenticationRepositoryImpl(Get.find<AuthenticationDataSource>()));
|
||||
Get.lazyPut(() => AuthenticationOIDCRepositoryImpl(Get.find<AuthenticationOIDCDataSource>()));
|
||||
}
|
||||
}
|
||||
@@ -5,7 +5,9 @@ import 'package:flutter/foundation.dart';
|
||||
import 'package:get/get.dart';
|
||||
import 'package:model/model.dart';
|
||||
import 'package:tmail_ui_user/features/login/domain/state/authentication_user_state.dart';
|
||||
import 'package:tmail_ui_user/features/login/domain/state/check_oidc_is_available_state.dart';
|
||||
import 'package:tmail_ui_user/features/login/domain/usecases/authentication_user_interactor.dart';
|
||||
import 'package:tmail_ui_user/features/login/domain/usecases/check_oidc_is_available_interactor.dart';
|
||||
import 'package:tmail_ui_user/features/login/presentation/login_form_type.dart';
|
||||
import 'package:tmail_ui_user/features/login/presentation/state/login_state.dart';
|
||||
import 'package:tmail_ui_user/main/routes/app_routes.dart';
|
||||
@@ -17,6 +19,7 @@ class LoginController extends GetxController {
|
||||
final AuthenticationInteractor _authenticationInteractor;
|
||||
final DynamicUrlInterceptors _dynamicUrlInterceptors;
|
||||
final AuthorizationInterceptors _authorizationInterceptors;
|
||||
final CheckOIDCIsAvailableInteractor _checkOIDCIsAvailableInteractor;
|
||||
|
||||
final TextEditingController urlInputController = TextEditingController();
|
||||
|
||||
@@ -24,6 +27,7 @@ class LoginController extends GetxController {
|
||||
this._authenticationInteractor,
|
||||
this._dynamicUrlInterceptors,
|
||||
this._authorizationInterceptors,
|
||||
this._checkOIDCIsAvailableInteractor,
|
||||
);
|
||||
|
||||
var loginState = LoginState(Right(LoginInitAction())).obs;
|
||||
@@ -33,20 +37,48 @@ class LoginController extends GetxController {
|
||||
String? _userNameText;
|
||||
String? _passwordText;
|
||||
|
||||
void setUrlText(String url) => _urlText = url.formatURLValid();
|
||||
void setUrlText(String url) => _urlText = url.trim().formatURLValid();
|
||||
|
||||
void setUserNameText(String userName) => _userNameText = userName;
|
||||
|
||||
void setPasswordText(String password) => _passwordText = password;
|
||||
|
||||
Uri? _parseUri(String? url) => url != null ? Uri.parse(url.trim()) : null;
|
||||
Uri? _parseUri(String? url) => url != null && url.trim().isNotEmpty
|
||||
? Uri.parse(url.trim())
|
||||
: null;
|
||||
|
||||
UserName? _parseUserName(String? userName) => userName != null ? UserName(userName.trim()) : null;
|
||||
UserName? _parseUserName(String? userName) => userName != null && userName.trim().isNotEmpty
|
||||
? UserName(userName.trim())
|
||||
: null;
|
||||
|
||||
Password? _parsePassword(String? password) => password != null ? Password(password.trim()) : null;
|
||||
Password? _parsePassword(String? password) => password != null && password.trim().isNotEmpty
|
||||
? Password(password.trim())
|
||||
: null;
|
||||
|
||||
void handleNextInUrlInputFormPress() {
|
||||
completeInputUrlAction();
|
||||
_checkOIDCIsAvailable();
|
||||
}
|
||||
|
||||
void _checkOIDCIsAvailable() async {
|
||||
final baseUri = BuildUtils.isWeb ? _parseUri(AppConfig.baseUrl) : _parseUri(_urlText);
|
||||
|
||||
if (baseUri == null) {
|
||||
loginState.value = LoginState(Left(LoginMissUrlAction()));
|
||||
} else {
|
||||
loginState.value = LoginState(Right(LoginLoadingAction()));
|
||||
await _checkOIDCIsAvailableInteractor
|
||||
.execute(OIDCRequest(baseUrl: baseUri.path))
|
||||
.then((response) => response.fold(
|
||||
(failure) => _showFormLoginWithCredentialAction(),
|
||||
(success) => success is CheckOIDCIsAvailableSuccess
|
||||
? _showFormLoginWithSSOAction(success)
|
||||
: _showFormLoginWithCredentialAction()));
|
||||
}
|
||||
}
|
||||
|
||||
void _showFormLoginWithSSOAction(CheckOIDCIsAvailableSuccess success) {
|
||||
loginState.value = LoginState(Right(success));
|
||||
loginFormType.value = LoginFormType.ssoForm;
|
||||
}
|
||||
|
||||
void handleBackInCredentialForm() {
|
||||
@@ -54,23 +86,27 @@ class LoginController extends GetxController {
|
||||
loginFormType.value = LoginFormType.baseUrlForm;
|
||||
}
|
||||
|
||||
void completeInputUrlAction() {
|
||||
void _showFormLoginWithCredentialAction() {
|
||||
loginState.value = LoginState(Right(InputUrlCompletion()));
|
||||
loginFormType.value = LoginFormType.credentialForm;
|
||||
}
|
||||
|
||||
void handleLoginPressed() {
|
||||
final baseUri = kIsWeb ? _parseUri(AppConfig.baseUrl) : _parseUri(_urlText);
|
||||
final userName = _parseUserName(_userNameText);
|
||||
final password = _parsePassword(_passwordText);
|
||||
if (baseUri != null && userName != null && password != null) {
|
||||
_loginAction(baseUri, userName, password);
|
||||
} else if (baseUri == null) {
|
||||
loginState.value = LoginState(Left(LoginMissUrlAction()));
|
||||
} else if (userName == null) {
|
||||
loginState.value = LoginState(Left(LoginMissUsernameAction()));
|
||||
} else if (password == null) {
|
||||
loginState.value = LoginState(Left(LoginMissPasswordAction()));
|
||||
if (loginFormType.value == LoginFormType.ssoForm) {
|
||||
|
||||
} else {
|
||||
final baseUri = kIsWeb ? _parseUri(AppConfig.baseUrl) : _parseUri(_urlText);
|
||||
final userName = _parseUserName(_userNameText);
|
||||
final password = _parsePassword(_passwordText);
|
||||
if (baseUri != null && userName != null && password != null) {
|
||||
_loginAction(baseUri, userName, password);
|
||||
} else if (baseUri == null) {
|
||||
loginState.value = LoginState(Left(LoginMissUrlAction()));
|
||||
} else if (userName == null) {
|
||||
loginState.value = LoginState(Left(LoginMissUsernameAction()));
|
||||
} else if (password == null) {
|
||||
loginState.value = LoginState(Left(LoginMissPasswordAction()));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -1,3 +1,3 @@
|
||||
enum LoginFormType {
|
||||
none, baseUrlForm, credentialForm
|
||||
none, baseUrlForm, credentialForm, ssoForm
|
||||
}
|
||||
@@ -26,9 +26,11 @@ class LoginView extends BaseLoginView {
|
||||
children: [
|
||||
Center(child: SingleChildScrollView(child: _buildCenterForm(context), scrollDirection: Axis.vertical)),
|
||||
Obx(() {
|
||||
return loginController.loginFormType.value == LoginFormType.credentialForm
|
||||
? _buildBackButton(context)
|
||||
: const SizedBox.shrink();
|
||||
if (loginController.loginFormType.value == LoginFormType.credentialForm
|
||||
|| loginController.loginFormType.value == LoginFormType.ssoForm) {
|
||||
return _buildBackButton(context);
|
||||
}
|
||||
return const SizedBox.shrink();
|
||||
})
|
||||
]
|
||||
)
|
||||
@@ -36,9 +38,11 @@ class LoginView extends BaseLoginView {
|
||||
children: [
|
||||
_buildCenterForm(context),
|
||||
Obx(() {
|
||||
return loginController.loginFormType.value == LoginFormType.credentialForm
|
||||
? _buildBackButton(context)
|
||||
: const SizedBox.shrink();
|
||||
if (loginController.loginFormType.value == LoginFormType.credentialForm
|
||||
|| loginController.loginFormType.value == LoginFormType.ssoForm) {
|
||||
return _buildBackButton(context);
|
||||
}
|
||||
return const SizedBox.shrink();
|
||||
})
|
||||
]
|
||||
),
|
||||
@@ -78,15 +82,23 @@ class LoginView extends BaseLoginView {
|
||||
Obx(() {
|
||||
switch (controller.loginFormType.value) {
|
||||
case LoginFormType.baseUrlForm:
|
||||
return _supportScrollForm(context)
|
||||
? _buildNextButton(context)
|
||||
: _buildExpandedButton(context, _buildNextButton(context));
|
||||
return Obx(() => loginController.loginState.value.viewState.fold(
|
||||
(failure) => _buildNextButtonInContext(context),
|
||||
(success) => success is LoginLoadingAction
|
||||
? _buildLoadingCircularProgress()
|
||||
: _buildNextButtonInContext(context)));
|
||||
case LoginFormType.credentialForm:
|
||||
return Obx(() => loginController.loginState.value.viewState.fold(
|
||||
(failure) => _buildLoginButtonInContext(context),
|
||||
(success) => success is LoginLoadingAction
|
||||
? _buildLoadingCircularProgress()
|
||||
: _buildLoginButtonInContext(context)));
|
||||
case LoginFormType.ssoForm:
|
||||
return Obx(() => loginController.loginState.value.viewState.fold(
|
||||
(failure) => _buildLoginButtonInContext(context),
|
||||
(success) => success is LoginLoadingAction
|
||||
? _buildLoadingCircularProgress()
|
||||
: _buildLoginButtonInContext(context)));
|
||||
default:
|
||||
return const SizedBox.shrink();
|
||||
}
|
||||
@@ -166,6 +178,12 @@ class LoginView extends BaseLoginView {
|
||||
);
|
||||
}
|
||||
|
||||
Widget _buildNextButtonInContext(BuildContext context) {
|
||||
return _supportScrollForm(context)
|
||||
? _buildNextButton(context)
|
||||
: _buildExpandedButton(context, _buildNextButton(context));
|
||||
}
|
||||
|
||||
Widget _buildLoginButtonInContext(BuildContext context) {
|
||||
return _supportScrollForm(context)
|
||||
? buildLoginButton(context)
|
||||
|
||||
@@ -1387,4 +1387,10 @@ class AppLocalizations {
|
||||
name: 'thisEmailAddressInvalid',
|
||||
);
|
||||
}
|
||||
|
||||
String get loginInputSSOMessage {
|
||||
return Intl.message(
|
||||
'Sign-in with my SSO account',
|
||||
name: 'loginInputSSOMessage');
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user