TF-571 Get token from authentication sso
This commit is contained in:
@@ -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');
|
||||
}
|
||||
}
|
||||
@@ -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';
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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];
|
||||
}
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -153,6 +153,9 @@ dependencies:
|
||||
|
||||
timeago: 3.2.2
|
||||
|
||||
# OIDC Mobile
|
||||
flutter_appauth: 4.0.0
|
||||
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
sdk: flutter
|
||||
|
||||
Reference in New Issue
Block a user