TF-571 Get token from authentication sso

This commit is contained in:
dab246
2022-05-20 15:17:57 +07:00
committed by Dat H. Pham
parent ffb5cadc16
commit 4804a1659a
20 changed files with 227 additions and 11 deletions
+1 -1
View File
@@ -31,7 +31,7 @@
<key>CFBundleURLSchemes</key>
<array>
<string>ShareMedia-$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<string>teammail-mobile</string>
<string>teammail.mobile</string>
</array>
</dict>
</array>
@@ -5,4 +5,6 @@ abstract class AuthenticationOIDCDataSource {
Future<bool> checkOIDCIsAvailable(OIDCRequest oidcRequest);
Future<OIDCConfiguration> getOIDCConfiguration(Uri baseUri);
Future<TokenOIDC> getTokenOIDC(String clientId, String redirectUrl, String discoveryUrl, List<String> scopes);
}
@@ -26,4 +26,13 @@ class AuthenticationOIDCDataSourceImpl extends AuthenticationOIDCDataSource {
throw error;
});
}
@override
Future<TokenOIDC> getTokenOIDC(String clientId, String redirectUrl, String discoveryUrl, List<String> scopes) {
return Future.sync(() async {
return await _oidcHttpClient.getTokenOIDC(clientId, redirectUrl, discoveryUrl, scopes);
}).catchError((error) {
throw error;
});
}
}
@@ -0,0 +1,14 @@
import 'package:flutter_appauth/flutter_appauth.dart';
import 'package:model/model.dart';
extension AuthorizationTokenResponseExtension on AuthorizationTokenResponse {
TokenOIDC toTokenOIDC() {
return TokenOIDC(
accessToken ?? '',
TokenId(idToken ?? ''),
refreshToken ?? '',
expiredTime: accessTokenExpirationDateTime ?? DateTime.now());
}
}
@@ -2,15 +2,18 @@
import 'dart:convert';
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';
class OIDCHttpClient {
final DioClient _dioClient;
final FlutterAppAuth _appAuth;
OIDCHttpClient(this._dioClient);
OIDCHttpClient(this._dioClient, this._appAuth);
Future<OIDCResponse?> checkOIDCIsAvailable(OIDCRequest oidcRequest) async {
final result = await _dioClient.get(
@@ -33,4 +36,22 @@ class OIDCHttpClient {
Future<OIDCConfiguration> getOIDCConfiguration(Uri baseUri) async {
return OIDCConfiguration.initial();
}
Future<TokenOIDC> getTokenOIDC(String clientId, String redirectUrl, String discoveryUrl, List<String> scopes) async {
final tokenResponse = await _appAuth.authorizeAndExchangeCode(AuthorizationTokenRequest(
clientId,
redirectUrl,
discoveryUrl: discoveryUrl,
scopes: scopes,
preferEphemeralSession: true));
log('OIDCHttpClient::getTokenOIDC(): token: ${tokenResponse?.accessToken}');
if (tokenResponse != null) {
final tokenOIDC = tokenResponse.toTokenOIDC();
return tokenOIDC.isTokenValid() ? tokenOIDC : TokenOIDC.empty();
} else {
return TokenOIDC.empty();
}
}
}
@@ -1,5 +1,6 @@
import 'package:model/oidc/oidc_configuration.dart';
import 'package:model/oidc/request/oidc_request.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';
@@ -17,4 +18,9 @@ class AuthenticationOIDCRepositoryImpl extends AuthenticationOIDCRepository {
Future<OIDCConfiguration> getOIDCConfiguration(Uri baseUri) {
return _oidcDataSource.getOIDCConfiguration(baseUri);
}
@override
Future<TokenOIDC> getTokenOIDC(String clientId, String redirectUrl, String discoveryUrl, List<String> scopes) {
return _oidcDataSource.getTokenOIDC(clientId, redirectUrl, discoveryUrl, scopes);
}
}
@@ -1,9 +1,10 @@
import 'package:model/oidc/oidc_configuration.dart';
import 'package:model/oidc/request/oidc_request.dart';
import 'package:model/model.dart';
abstract class AuthenticationOIDCRepository {
Future<bool> checkOIDCIsAvailable(OIDCRequest oidcRequest);
Future<OIDCConfiguration> getOIDCConfiguration(Uri baseUri);
Future<TokenOIDC> getTokenOIDC(String clientId, String redirectUrl, String discoveryUrl, List<String> scopes);
}
@@ -0,0 +1,21 @@
import 'package:core/core.dart';
import 'package:model/model.dart';
class GetTokenOIDCSuccess extends UIState {
final TokenOIDC tokenOIDC;
GetTokenOIDCSuccess(this.tokenOIDC);
@override
List<Object> get props => [tokenOIDC];
}
class GetTokenOIDCFailure extends FeatureFailure {
final dynamic exception;
GetTokenOIDCFailure(this.exception);
@override
List<Object> get props => [exception];
}
@@ -0,0 +1,22 @@
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_token_oidc_state.dart';
class GetTokenOIDCInteractor {
final AuthenticationOIDCRepository authenticationOIDCRepository;
GetTokenOIDCInteractor(this.authenticationOIDCRepository);
Future<Either<Failure, Success>> execute(String clientId, String redirectUrl,
String discoveryUrl, List<String> scopes) async {
try {
final tokenOIDC = await authenticationOIDCRepository.getTokenOIDC(clientId, redirectUrl, discoveryUrl, scopes);
return Right<Failure, Success>(GetTokenOIDCSuccess(tokenOIDC));
} catch (e) {
return Left<Failure, Success>(GetTokenOIDCFailure(e));
}
}
}
@@ -7,6 +7,7 @@ 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/domain/state/get_token_oidc_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';
@@ -39,6 +40,8 @@ abstract class BaseLoginView extends GetWidget<LoginController> {
} else if (failure is GetOIDCConfigurationFailure
|| failure is LoginCanNotVerifySSOConfigurationAction) {
return AppLocalizations.of(context).canNotVerifySSOConfiguration;
} else if (failure is GetTokenOIDCFailure || failure is LoginCanNotGetTokenAction) {
return AppLocalizations.of(context).canNotGetToken;
} else {
return AppLocalizations.of(context).unknown_error_login_message;
}
@@ -16,16 +16,11 @@ import 'package:tmail_ui_user/features/login/domain/repository/credential_reposi
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/domain/usecases/get_token_oidc_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(
@@ -34,6 +29,7 @@ class LoginBindings extends BaseBindings {
Get.find<AuthorizationInterceptors>(),
Get.find<CheckOIDCIsAvailableInteractor>(),
Get.find<GetOIDCConfigurationInteractor>(),
Get.find<GetTokenOIDCInteractor>(),
));
}
@@ -61,6 +57,9 @@ class LoginBindings extends BaseBindings {
Get.lazyPut(() => GetOIDCConfigurationInteractor(
Get.find<AuthenticationOIDCRepository>(),
));
Get.lazyPut(() => GetTokenOIDCInteractor(
Get.find<AuthenticationOIDCRepository>(),
));
}
@override
@@ -7,9 +7,11 @@ 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/state/get_token_oidc_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/domain/usecases/get_token_oidc_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';
@@ -23,6 +25,7 @@ class LoginController extends GetxController {
final AuthorizationInterceptors _authorizationInterceptors;
final CheckOIDCIsAvailableInteractor _checkOIDCIsAvailableInteractor;
final GetOIDCConfigurationInteractor _getOIDCConfigurationInteractor;
final GetTokenOIDCInteractor _getTokenOIDCInteractor;
final TextEditingController urlInputController = TextEditingController();
@@ -32,6 +35,7 @@ class LoginController extends GetxController {
this._authorizationInterceptors,
this._checkOIDCIsAvailableInteractor,
this._getOIDCConfigurationInteractor,
this._getTokenOIDCInteractor,
);
var loginState = LoginState(Right(LoginInitAction())).obs;
@@ -141,6 +145,35 @@ class LoginController extends GetxController {
void _getOIDCConfigurationSuccess(GetOIDCConfigurationSuccess success) {
loginState.value = LoginState(Right(success));
if (currentContext != null) {
_getTokenOIDCAction(currentContext!, success.oidcConfiguration);
}
}
void _getTokenOIDCAction(BuildContext context, OIDCConfiguration config) async {
loginState.value = LoginState(Right(LoginLoadingAction()));
await _getTokenOIDCInteractor
.execute(config.clientId, config.redirectUrl, config.discoveryUrl, config.scopes)
.then((response) => response.fold(
(failure) {
if (failure is GetTokenOIDCFailure) {
loginState.value = LoginState(Left(failure));
} else {
loginState.value = LoginState(Left(LoginCanNotGetTokenAction()));
}
},
(success) {
if (success is GetTokenOIDCSuccess) {
_getTokenOIDCSuccess(success);
} else {
loginState.value = LoginState(Left(LoginCanNotGetTokenAction()));
}
}));
}
void _getTokenOIDCSuccess(GetTokenOIDCSuccess success) {
log('LoginController::_getTokenOIDCSuccess(): ');
loginState.value = LoginState(Right(success));
}
void _loginAction(Uri baseUrl, UserName userName, Password password) async {
@@ -47,4 +47,10 @@ class LoginMissPasswordAction extends Failure {
class LoginCanNotVerifySSOConfigurationAction extends Failure {
@override
List<Object?> get props => [];
}
@immutable
class LoginCanNotGetTokenAction extends Failure {
@override
List<Object?> get props => [];
}
@@ -4,10 +4,12 @@ import 'package:connectivity_plus/connectivity_plus.dart';
import 'package:core/core.dart';
import 'package:dio/dio.dart';
import 'package:flutter/foundation.dart';
import 'package:flutter_appauth/flutter_appauth.dart';
import 'package:get/get.dart';
import 'package:jmap_dart_client/http/http_client.dart' as JmapHttpClient;
import 'package:tmail_ui_user/features/composer/data/network/composer_api.dart';
import 'package:tmail_ui_user/features/email/data/network/email_api.dart';
import 'package:tmail_ui_user/features/login/data/network/oidc_http_client.dart';
import 'package:tmail_ui_user/features/mailbox/data/network/mailbox_api.dart';
import 'package:tmail_ui_user/features/session/data/network/session_api.dart';
import 'package:tmail_ui_user/features/thread/data/network/thread_api.dart';
@@ -57,6 +59,8 @@ class NetworkBindings extends Bindings {
Get.find<JmapHttpClient.HttpClient>(),
Get.find<DownloadManager>()));
Get.put(ComposerAPI(Get.find<DioClient>()));
Get.put(const FlutterAppAuth());
Get.put(OIDCHttpClient(Get.find<DioClient>(), Get.find<FlutterAppAuth>()));
}
void _bindingConnection() {
@@ -1399,4 +1399,10 @@ class AppLocalizations {
'Can not verify SSO configuration, please check with your system administrator',
name: 'canNotVerifySSOConfiguration');
}
String get canNotGetToken {
return Intl.message(
'Can not get token, please check with your system administrator',
name: 'canNotGetToken');
}
}
+4 -1
View File
@@ -77,4 +77,7 @@ export 'identity/identity_request_dto.dart';
export 'oidc/response/oidc_response.dart';
export 'oidc/response/oidc_link_dto.dart';
export 'oidc/request/oidc_request.dart';
export 'oidc/oidc_configuration.dart';
export 'oidc/oidc_configuration.dart';
export 'oidc/token_id.dart';
export 'oidc/token_oidc.dart';
export 'oidc/token.dart';
+17
View File
@@ -0,0 +1,17 @@
import 'package:equatable/equatable.dart';
import 'package:model/oidc/token_id.dart';
class Token extends Equatable {
const Token(this.token, this.tokenId);
final String token;
final TokenId tokenId;
@override
List<Object> get props => [token, tokenId];
}
extension TokenExtension on Token {
bool isTokenValid() => token.isNotEmpty && tokenId.uuid.isNotEmpty;
}
+11
View File
@@ -0,0 +1,11 @@
import 'package:equatable/equatable.dart';
class TokenId with EquatableMixin {
final String uuid;
TokenId(this.uuid);
@override
List<Object> get props => [uuid];
}
+35
View File
@@ -0,0 +1,35 @@
import 'package:equatable/equatable.dart';
import 'package:model/oidc/token.dart';
import 'package:model/oidc/token_id.dart';
class TokenOIDC with EquatableMixin {
final String token;
final TokenId tokenId;
final DateTime? expiredTime;
final String refreshToken;
TokenOIDC(
this.token,
this.tokenId,
this.refreshToken,
{this.expiredTime}
);
factory TokenOIDC.empty() {
return TokenOIDC('', TokenId(''), '');
}
@override
List<Object?> get props => [token, tokenId, expiredTime, refreshToken];
}
extension TokenOIDCExtension on TokenOIDC {
bool isTokenValid() => token.isNotEmpty && tokenId.uuid.isNotEmpty;
Token toToken() {
return Token(token, tokenId);
}
}
+3
View File
@@ -153,6 +153,9 @@ dependencies:
timeago: 3.2.2
# OIDC Mobile
flutter_appauth: 4.0.0
dev_dependencies:
flutter_test:
sdk: flutter