TF-571 Setup and get OIDC configuration

This commit is contained in:
dab246
2022-05-20 13:35:03 +07:00
committed by Dat H. Pham
parent 239234f836
commit ffb5cadc16
17 changed files with 177 additions and 5 deletions
+3
View File
@@ -40,6 +40,9 @@ android {
versionName flutterVersionName
// The number of method references in a .dex file cannot exceed 64K
multiDexEnabled true
manifestPlaceholders = [
'appAuthRedirectScheme': 'teammail.mobile'
]
}
buildTypes {
+5
View File
@@ -16,6 +16,11 @@
<category android:name="android.intent.category.BROWSABLE" />
<data android:scheme="https" />
</intent>
<intent>
<action android:name="android.intent.action.VIEW" />
<category android:name="android.intent.category.APP_BROWSER" />
<data android:scheme="https" />
</intent>
</queries>
<application
+1
View File
@@ -31,6 +31,7 @@
<key>CFBundleURLSchemes</key>
<array>
<string>ShareMedia-$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<string>teammail-mobile</string>
</array>
</dict>
</array>
@@ -3,4 +3,6 @@ import 'package:model/model.dart';
abstract class AuthenticationOIDCDataSource {
Future<bool> checkOIDCIsAvailable(OIDCRequest oidcRequest);
Future<OIDCConfiguration> getOIDCConfiguration(Uri baseUri);
}
@@ -17,4 +17,13 @@ class AuthenticationOIDCDataSourceImpl extends AuthenticationOIDCDataSource {
throw error;
});
}
@override
Future<OIDCConfiguration> getOIDCConfiguration(Uri baseUri) {
return Future.sync(() async {
return await _oidcHttpClient.getOIDCConfiguration(baseUri);
}).catchError((error) {
throw error;
});
}
}
@@ -29,4 +29,8 @@ class OIDCHttpClient {
return OIDCResponse.fromJson(jsonDecode(result));
}
}
Future<OIDCConfiguration> getOIDCConfiguration(Uri baseUri) async {
return OIDCConfiguration.initial();
}
}
@@ -1,3 +1,4 @@
import 'package:model/oidc/oidc_configuration.dart';
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';
@@ -11,4 +12,9 @@ class AuthenticationOIDCRepositoryImpl extends AuthenticationOIDCRepository {
Future<bool> checkOIDCIsAvailable(OIDCRequest oidcRequest) {
return _oidcDataSource.checkOIDCIsAvailable(oidcRequest);
}
@override
Future<OIDCConfiguration> getOIDCConfiguration(Uri baseUri) {
return _oidcDataSource.getOIDCConfiguration(baseUri);
}
}
@@ -1,6 +1,9 @@
import 'package:model/oidc/oidc_configuration.dart';
import 'package:model/oidc/request/oidc_request.dart';
abstract class AuthenticationOIDCRepository {
Future<bool> checkOIDCIsAvailable(OIDCRequest oidcRequest);
Future<OIDCConfiguration> getOIDCConfiguration(Uri baseUri);
}
@@ -0,0 +1,21 @@
import 'package:core/core.dart';
import 'package:model/model.dart';
class GetOIDCConfigurationSuccess extends UIState {
final OIDCConfiguration oidcConfiguration;
GetOIDCConfigurationSuccess(this.oidcConfiguration);
@override
List<Object> get props => [oidcConfiguration];
}
class GetOIDCConfigurationFailure extends FeatureFailure {
final dynamic exception;
GetOIDCConfigurationFailure(this.exception);
@override
List<Object> get props => [exception];
}
@@ -0,0 +1,21 @@
import 'package:core/core.dart';
import 'package:dartz/dartz.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';
class GetOIDCConfigurationInteractor {
final AuthenticationOIDCRepository _oidcRepository;
GetOIDCConfigurationInteractor(this._oidcRepository);
Future<Either<Failure, Success>> execute(Uri baseUri) async {
try {
final oidcConfiguration = await _oidcRepository.getOIDCConfiguration(baseUri);
log('GetOIDCConfigurationInteractor::execute(): oidcConfiguration: $oidcConfiguration');
return Right<Failure, Success>(GetOIDCConfigurationSuccess(oidcConfiguration));
} catch (e) {
log('GetOIDCConfigurationInteractor::execute(): ERROR: $e');
return Left<Failure, Success>(GetOIDCConfigurationFailure(e));
}
}
}
@@ -6,6 +6,7 @@ import 'package:core/presentation/views/text/text_field_builder.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter/material.dart';
import 'package:get/get.dart';
import 'package:tmail_ui_user/features/login/domain/state/get_oidc_configuration_state.dart';
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/login/presentation/state/login_state.dart';
@@ -35,6 +36,9 @@ abstract class BaseLoginView extends GetWidget<LoginController> {
return AppLocalizations.of(context).requiredEmail;
} else if (failure is LoginMissPasswordAction) {
return AppLocalizations.of(context).requiredPassword;
} else if (failure is GetOIDCConfigurationFailure
|| failure is LoginCanNotVerifySSOConfigurationAction) {
return AppLocalizations.of(context).canNotVerifySSOConfiguration;
} else {
return AppLocalizations.of(context).unknown_error_login_message;
}
@@ -15,6 +15,7 @@ import 'package:tmail_ui_user/features/login/domain/repository/authentication_re
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/domain/usecases/get_oidc_configuration_interactor.dart';
import 'package:tmail_ui_user/features/login/presentation/login_controller.dart';
class LoginBindings extends BaseBindings {
@@ -32,6 +33,7 @@ class LoginBindings extends BaseBindings {
Get.find<DynamicUrlInterceptors>(),
Get.find<AuthorizationInterceptors>(),
Get.find<CheckOIDCIsAvailableInteractor>(),
Get.find<GetOIDCConfigurationInteractor>(),
));
}
@@ -56,6 +58,9 @@ class LoginBindings extends BaseBindings {
Get.lazyPut(() => CheckOIDCIsAvailableInteractor(
Get.find<AuthenticationOIDCRepository>(),
));
Get.lazyPut(() => GetOIDCConfigurationInteractor(
Get.find<AuthenticationOIDCRepository>(),
));
}
@override
@@ -6,8 +6,10 @@ 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/state/get_oidc_configuration_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/domain/usecases/get_oidc_configuration_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';
@@ -20,6 +22,7 @@ class LoginController extends GetxController {
final DynamicUrlInterceptors _dynamicUrlInterceptors;
final AuthorizationInterceptors _authorizationInterceptors;
final CheckOIDCIsAvailableInteractor _checkOIDCIsAvailableInteractor;
final GetOIDCConfigurationInteractor _getOIDCConfigurationInteractor;
final TextEditingController urlInputController = TextEditingController();
@@ -28,6 +31,7 @@ class LoginController extends GetxController {
this._dynamicUrlInterceptors,
this._authorizationInterceptors,
this._checkOIDCIsAvailableInteractor,
this._getOIDCConfigurationInteractor,
);
var loginState = LoginState(Right(LoginInitAction())).obs;
@@ -61,13 +65,13 @@ class LoginController extends GetxController {
void _checkOIDCIsAvailable() async {
final baseUri = BuildUtils.isWeb ? _parseUri(AppConfig.baseUrl) : _parseUri(_urlText);
log('LoginController::_checkOIDCIsAvailable(): baseUri: $baseUri');
if (baseUri == null) {
loginState.value = LoginState(Left(LoginMissUrlAction()));
} else {
loginState.value = LoginState(Right(LoginLoadingAction()));
await _checkOIDCIsAvailableInteractor
.execute(OIDCRequest(baseUrl: baseUri.path))
.execute(OIDCRequest(baseUrl: baseUri.origin))
.then((response) => response.fold(
(failure) => _showFormLoginWithCredentialAction(),
(success) => success is CheckOIDCIsAvailableSuccess
@@ -93,7 +97,12 @@ class LoginController extends GetxController {
void handleLoginPressed() {
if (loginFormType.value == LoginFormType.ssoForm) {
final baseUri = kIsWeb ? _parseUri(AppConfig.baseUrl) : _parseUri(_urlText);
if (baseUri != null) {
_getOIDCConfiguration(baseUri);
} else {
loginState.value = LoginState(Left(LoginMissUrlAction()));
}
} else {
final baseUri = kIsWeb ? _parseUri(AppConfig.baseUrl) : _parseUri(_urlText);
final userName = _parseUserName(_userNameText);
@@ -110,6 +119,30 @@ class LoginController extends GetxController {
}
}
void _getOIDCConfiguration(Uri baseUri) async {
loginState.value = LoginState(Right(LoginLoadingAction()));
await _getOIDCConfigurationInteractor.execute(baseUri)
.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()));
}
}));
}
void _getOIDCConfigurationSuccess(GetOIDCConfigurationSuccess success) {
loginState.value = LoginState(Right(success));
}
void _loginAction(Uri baseUrl, UserName userName, Password password) async {
loginState.value = LoginState(Right(LoginLoadingAction()));
await _authenticationInteractor.execute(baseUrl, userName, password)
@@ -125,11 +158,12 @@ class LoginController extends GetxController {
pushAndPop(AppRoutes.SESSION);
}
void _loginFailureAction(AuthenticationUserFailure failure) {
void _loginFailureAction(FeatureFailure failure) {
loginState.value = LoginState(Left(failure));
}
void formatUrl(String url) {
log('LoginController::formatUrl(): $url');
if (url.isValid()) {
urlInputController.text = url.removePrefix();
}
@@ -41,4 +41,10 @@ class LoginMissUsernameAction extends Failure {
class LoginMissPasswordAction extends Failure {
@override
List<Object?> get props => [];
}
@immutable
class LoginCanNotVerifySSOConfigurationAction extends Failure {
@override
List<Object?> get props => [];
}
@@ -1393,4 +1393,10 @@ class AppLocalizations {
'Sign-in with my SSO account',
name: 'loginInputSSOMessage');
}
String get canNotVerifySSOConfiguration {
return Intl.message(
'Can not verify SSO configuration, please check with your system administrator',
name: 'canNotVerifySSOConfiguration');
}
}
+2 -1
View File
@@ -76,4 +76,5 @@ export 'identity/identity_request_dto.dart';
// OIDC
export 'oidc/response/oidc_response.dart';
export 'oidc/response/oidc_link_dto.dart';
export 'oidc/request/oidc_request.dart';
export 'oidc/request/oidc_request.dart';
export 'oidc/oidc_configuration.dart';
+41
View File
@@ -0,0 +1,41 @@
import 'package:equatable/equatable.dart';
class OIDCConfiguration with EquatableMixin {
static const redirectOidc = 'teammail.mobile://oauthredirect';
static const wellKnownOpenId = '.well-known/openid-configuration';
final String authority;
final String clientId;
final String redirectUrl = redirectOidc;
final List<String> scopes;
OIDCConfiguration({
required this.authority,
required this.clientId,
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;
} else {
return authority + '/' + wellKnownOpenId;
}
}
@override
List<Object?> get props => [
authority,
clientId,
scopes
];
}