TF-571 Use response from webfinger to get OIDC from authority
This commit is contained in:
committed by
Dat H. Pham
parent
ea598c20b8
commit
a48cd35d4f
@@ -2,9 +2,9 @@
|
|||||||
import 'package:model/model.dart';
|
import 'package:model/model.dart';
|
||||||
|
|
||||||
abstract class AuthenticationOIDCDataSource {
|
abstract class AuthenticationOIDCDataSource {
|
||||||
Future<bool> checkOIDCIsAvailable(OIDCRequest oidcRequest);
|
Future<OIDCResponse> checkOIDCIsAvailable(OIDCRequest oidcRequest);
|
||||||
|
|
||||||
Future<OIDCConfiguration> getOIDCConfiguration(Uri baseUri);
|
Future<OIDCConfiguration> getOIDCConfiguration(Uri baseUri, OIDCResponse oidcResponse);
|
||||||
|
|
||||||
Future<TokenOIDC> getTokenOIDC(String clientId, String redirectUrl, String discoveryUrl, List<String> scopes);
|
Future<TokenOIDC> getTokenOIDC(String clientId, String redirectUrl, String discoveryUrl, List<String> scopes);
|
||||||
}
|
}
|
||||||
@@ -9,19 +9,19 @@ class AuthenticationOIDCDataSourceImpl extends AuthenticationOIDCDataSource {
|
|||||||
AuthenticationOIDCDataSourceImpl(this._oidcHttpClient);
|
AuthenticationOIDCDataSourceImpl(this._oidcHttpClient);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<bool> checkOIDCIsAvailable(OIDCRequest oidcRequest) {
|
Future<OIDCResponse> checkOIDCIsAvailable(OIDCRequest oidcRequest) {
|
||||||
return Future.sync(() async {
|
return Future.sync(() async {
|
||||||
final oidcResponse = await _oidcHttpClient.checkOIDCIsAvailable(oidcRequest);
|
final oidcResponse = await _oidcHttpClient.checkOIDCIsAvailable(oidcRequest);
|
||||||
return oidcResponse != null;
|
return oidcResponse!;
|
||||||
}).catchError((error) {
|
}).catchError((error) {
|
||||||
throw error;
|
throw error;
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<OIDCConfiguration> getOIDCConfiguration(Uri baseUri) {
|
Future<OIDCConfiguration> getOIDCConfiguration(Uri baseUri, OIDCResponse oidcResponse) {
|
||||||
return Future.sync(() async {
|
return Future.sync(() async {
|
||||||
return await _oidcHttpClient.getOIDCConfiguration(baseUri);
|
return await _oidcHttpClient.getOIDCConfiguration(baseUri, oidcResponse);
|
||||||
}).catchError((error) {
|
}).catchError((error) {
|
||||||
throw error;
|
throw error;
|
||||||
});
|
});
|
||||||
|
|||||||
@@ -0,0 +1,4 @@
|
|||||||
|
class OIDCConstant {
|
||||||
|
static String get mobileOidcClientId => 'teammail-mobile';
|
||||||
|
static List<String> get oidcScope => ['openid', 'offline_access'];
|
||||||
|
}
|
||||||
@@ -0,0 +1 @@
|
|||||||
|
class CanNotFoundOIDCAuthority implements Exception {}
|
||||||
@@ -5,8 +5,10 @@ import 'package:core/core.dart';
|
|||||||
import 'package:flutter_appauth/flutter_appauth.dart';
|
import 'package:flutter_appauth/flutter_appauth.dart';
|
||||||
import 'package:model/model.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/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/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 {
|
class OIDCHttpClient {
|
||||||
|
|
||||||
@@ -21,7 +23,7 @@ class OIDCHttpClient {
|
|||||||
.generateOIDCPath(Uri.parse(oidcRequest.baseUrl))
|
.generateOIDCPath(Uri.parse(oidcRequest.baseUrl))
|
||||||
.withQueryParameters([
|
.withQueryParameters([
|
||||||
StringQueryParameter('resource', oidcRequest.resourceUrl),
|
StringQueryParameter('resource', oidcRequest.resourceUrl),
|
||||||
StringQueryParameter('rel', oidcRequest.relUrl),
|
StringQueryParameter('rel', OIDCRequest.relUrl),
|
||||||
])
|
])
|
||||||
.generateEndpointPath()
|
.generateEndpointPath()
|
||||||
);
|
);
|
||||||
@@ -33,8 +35,16 @@ class OIDCHttpClient {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Future<OIDCConfiguration> getOIDCConfiguration(Uri baseUri) async {
|
Future<OIDCConfiguration> getOIDCConfiguration(Uri baseUri, OIDCResponse oidcResponse) async {
|
||||||
return OIDCConfiguration.initial();
|
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<TokenOIDC> getTokenOIDC(String clientId, String redirectUrl, String discoveryUrl, List<String> scopes) async {
|
Future<TokenOIDC> getTokenOIDC(String clientId, String redirectUrl, String discoveryUrl, List<String> scopes) async {
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import 'package:model/oidc/oidc_configuration.dart';
|
import 'package:model/oidc/oidc_configuration.dart';
|
||||||
import 'package:model/oidc/request/oidc_request.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: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/data/datasource/authentication_oidc_datasource.dart';
|
||||||
import 'package:tmail_ui_user/features/login/domain/repository/authentication_oidc_repository.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);
|
AuthenticationOIDCRepositoryImpl(this._oidcDataSource);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<bool> checkOIDCIsAvailable(OIDCRequest oidcRequest) {
|
Future<OIDCResponse> checkOIDCIsAvailable(OIDCRequest oidcRequest) {
|
||||||
return _oidcDataSource.checkOIDCIsAvailable(oidcRequest);
|
return _oidcDataSource.checkOIDCIsAvailable(oidcRequest);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
Future<OIDCConfiguration> getOIDCConfiguration(Uri baseUri) {
|
Future<OIDCConfiguration> getOIDCConfiguration(Uri baseUri, OIDCResponse oidcResponse) {
|
||||||
return _oidcDataSource.getOIDCConfiguration(baseUri);
|
return _oidcDataSource.getOIDCConfiguration(baseUri, oidcResponse);
|
||||||
}
|
}
|
||||||
|
|
||||||
@override
|
@override
|
||||||
|
|||||||
@@ -2,9 +2,9 @@
|
|||||||
import 'package:model/model.dart';
|
import 'package:model/model.dart';
|
||||||
|
|
||||||
abstract class AuthenticationOIDCRepository {
|
abstract class AuthenticationOIDCRepository {
|
||||||
Future<bool> checkOIDCIsAvailable(OIDCRequest oidcRequest);
|
Future<OIDCResponse> checkOIDCIsAvailable(OIDCRequest oidcRequest);
|
||||||
|
|
||||||
Future<OIDCConfiguration> getOIDCConfiguration(Uri baseUri);
|
Future<OIDCConfiguration> getOIDCConfiguration(Uri baseUri, OIDCResponse oidcResponse);
|
||||||
|
|
||||||
Future<TokenOIDC> getTokenOIDC(String clientId, String redirectUrl, String discoveryUrl, List<String> scopes);
|
Future<TokenOIDC> getTokenOIDC(String clientId, String redirectUrl, String discoveryUrl, List<String> scopes);
|
||||||
}
|
}
|
||||||
@@ -1,11 +1,13 @@
|
|||||||
import 'package:core/core.dart';
|
import 'package:core/core.dart';
|
||||||
|
import 'package:model/oidc/response/oidc_response.dart';
|
||||||
|
|
||||||
class CheckOIDCIsAvailableSuccess extends UIState {
|
class CheckOIDCIsAvailableSuccess extends UIState {
|
||||||
|
final OIDCResponse oidcResponse;
|
||||||
|
|
||||||
CheckOIDCIsAvailableSuccess();
|
CheckOIDCIsAvailableSuccess(this.oidcResponse);
|
||||||
|
|
||||||
@override
|
@override
|
||||||
List<Object> get props => [];
|
List<Object> get props => [oidcResponse];
|
||||||
}
|
}
|
||||||
|
|
||||||
class CheckOIDCIsAvailableFailure extends FeatureFailure {
|
class CheckOIDCIsAvailableFailure extends FeatureFailure {
|
||||||
|
|||||||
@@ -13,9 +13,7 @@ class CheckOIDCIsAvailableInteractor {
|
|||||||
try {
|
try {
|
||||||
final result = await _oidcRepository.checkOIDCIsAvailable(oidcRequest);
|
final result = await _oidcRepository.checkOIDCIsAvailable(oidcRequest);
|
||||||
log('CheckOIDCIsAvailableInteractor::execute(): result: $result');
|
log('CheckOIDCIsAvailableInteractor::execute(): result: $result');
|
||||||
return result
|
return Right<Failure, Success>(CheckOIDCIsAvailableSuccess(result));
|
||||||
? Right<Failure, Success>(CheckOIDCIsAvailableSuccess())
|
|
||||||
: Left<Failure, Success>(CheckOIDCIsAvailableFailure(null));
|
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
log('CheckOIDCIsAvailableInteractor::execute(): ERROR: $e');
|
log('CheckOIDCIsAvailableInteractor::execute(): ERROR: $e');
|
||||||
return Left<Failure, Success>(CheckOIDCIsAvailableFailure(e));
|
return Left<Failure, Success>(CheckOIDCIsAvailableFailure(e));
|
||||||
|
|||||||
@@ -1,5 +1,6 @@
|
|||||||
import 'package:core/core.dart';
|
import 'package:core/core.dart';
|
||||||
import 'package:dartz/dartz.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/repository/authentication_oidc_repository.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_oidc_configuration_state.dart';
|
||||||
|
|
||||||
@@ -8,9 +9,9 @@ class GetOIDCConfigurationInteractor {
|
|||||||
|
|
||||||
GetOIDCConfigurationInteractor(this._oidcRepository);
|
GetOIDCConfigurationInteractor(this._oidcRepository);
|
||||||
|
|
||||||
Future<Either<Failure, Success>> execute(Uri baseUri) async {
|
Future<Either<Failure, Success>> execute(Uri baseUri, OIDCResponse oidcResponse) async {
|
||||||
try {
|
try {
|
||||||
final oidcConfiguration = await _oidcRepository.getOIDCConfiguration(baseUri);
|
final oidcConfiguration = await _oidcRepository.getOIDCConfiguration(baseUri, oidcResponse);
|
||||||
log('GetOIDCConfigurationInteractor::execute(): oidcConfiguration: $oidcConfiguration');
|
log('GetOIDCConfigurationInteractor::execute(): oidcConfiguration: $oidcConfiguration');
|
||||||
return Right<Failure, Success>(GetOIDCConfigurationSuccess(oidcConfiguration));
|
return Right<Failure, Success>(GetOIDCConfigurationSuccess(oidcConfiguration));
|
||||||
} catch (e) {
|
} catch (e) {
|
||||||
|
|||||||
@@ -45,6 +45,7 @@ class LoginController extends GetxController {
|
|||||||
String? _urlText;
|
String? _urlText;
|
||||||
String? _userNameText;
|
String? _userNameText;
|
||||||
String? _passwordText;
|
String? _passwordText;
|
||||||
|
OIDCResponse? _oidcResponse;
|
||||||
|
|
||||||
void setUrlText(String url) => _urlText = url.trim().formatURLValid();
|
void setUrlText(String url) => _urlText = url.trim().formatURLValid();
|
||||||
|
|
||||||
@@ -75,8 +76,9 @@ class LoginController extends GetxController {
|
|||||||
loginState.value = LoginState(Left(LoginMissUrlAction()));
|
loginState.value = LoginState(Left(LoginMissUrlAction()));
|
||||||
} else {
|
} else {
|
||||||
loginState.value = LoginState(Right(LoginLoadingAction()));
|
loginState.value = LoginState(Right(LoginLoadingAction()));
|
||||||
|
log('LoginController::_checkOIDCIsAvailable(): origin: + ${baseUri.origin}');
|
||||||
await _checkOIDCIsAvailableInteractor
|
await _checkOIDCIsAvailableInteractor
|
||||||
.execute(OIDCRequest(baseUrl: baseUri.origin))
|
.execute(OIDCRequest(baseUrl: baseUri.origin, resourceUrl: baseUri.origin))
|
||||||
.then((response) => response.fold(
|
.then((response) => response.fold(
|
||||||
(failure) => _showFormLoginWithCredentialAction(),
|
(failure) => _showFormLoginWithCredentialAction(),
|
||||||
(success) => success is CheckOIDCIsAvailableSuccess
|
(success) => success is CheckOIDCIsAvailableSuccess
|
||||||
@@ -88,6 +90,7 @@ class LoginController extends GetxController {
|
|||||||
void _showFormLoginWithSSOAction(CheckOIDCIsAvailableSuccess success) {
|
void _showFormLoginWithSSOAction(CheckOIDCIsAvailableSuccess success) {
|
||||||
loginState.value = LoginState(Right(success));
|
loginState.value = LoginState(Right(success));
|
||||||
loginFormType.value = LoginFormType.ssoForm;
|
loginFormType.value = LoginFormType.ssoForm;
|
||||||
|
_oidcResponse = success.oidcResponse;
|
||||||
}
|
}
|
||||||
|
|
||||||
void handleBackInCredentialForm() {
|
void handleBackInCredentialForm() {
|
||||||
@@ -126,22 +129,26 @@ class LoginController extends GetxController {
|
|||||||
|
|
||||||
void _getOIDCConfiguration(Uri baseUri) async {
|
void _getOIDCConfiguration(Uri baseUri) async {
|
||||||
loginState.value = LoginState(Right(LoginLoadingAction()));
|
loginState.value = LoginState(Right(LoginLoadingAction()));
|
||||||
await _getOIDCConfigurationInteractor.execute(baseUri)
|
if (_oidcResponse != null) {
|
||||||
|
await _getOIDCConfigurationInteractor.execute(baseUri, _oidcResponse!)
|
||||||
.then((response) => response.fold(
|
.then((response) => response.fold(
|
||||||
(failure) {
|
(failure) {
|
||||||
if (failure is GetOIDCConfigurationFailure) {
|
if (failure is GetOIDCConfigurationFailure) {
|
||||||
loginState.value = LoginState(Left(failure));
|
loginState.value = LoginState(Left(failure));
|
||||||
} else {
|
} else {
|
||||||
loginState.value = LoginState(Left(LoginCanNotVerifySSOConfigurationAction()));
|
loginState.value = LoginState(
|
||||||
}
|
Left(LoginCanNotVerifySSOConfigurationAction()));
|
||||||
},
|
}
|
||||||
(success) {
|
},
|
||||||
if (success is GetOIDCConfigurationSuccess) {
|
(success) {
|
||||||
_getOIDCConfigurationSuccess(success);
|
if (success is GetOIDCConfigurationSuccess) {
|
||||||
} else {
|
_getOIDCConfigurationSuccess(success);
|
||||||
loginState.value = LoginState(Left(LoginCanNotVerifySSOConfigurationAction()));
|
} else {
|
||||||
}
|
loginState.value = LoginState(
|
||||||
}));
|
Left(LoginCanNotVerifySSOConfigurationAction()));
|
||||||
|
}
|
||||||
|
}));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
void _getOIDCConfigurationSuccess(GetOIDCConfigurationSuccess success) {
|
void _getOIDCConfigurationSuccess(GetOIDCConfigurationSuccess success) {
|
||||||
|
|||||||
@@ -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": "Initializing data...",
|
||||||
"@initializing_data": {
|
"@initializing_data": {
|
||||||
"type": "text",
|
"type": "text",
|
||||||
@@ -1353,5 +1353,23 @@
|
|||||||
"type": "text",
|
"type": "text",
|
||||||
"placeholders_order": [],
|
"placeholders_order": [],
|
||||||
"placeholders": {}
|
"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": {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -16,14 +16,6 @@ class OIDCConfiguration with EquatableMixin {
|
|||||||
required this.scopes
|
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 {
|
String get discoveryUrl {
|
||||||
if (authority.endsWith('/')) {
|
if (authority.endsWith('/')) {
|
||||||
return authority + wellKnownOpenId;
|
return authority + wellKnownOpenId;
|
||||||
|
|||||||
@@ -4,10 +4,10 @@ import 'package:equatable/equatable.dart';
|
|||||||
class OIDCRequest with EquatableMixin {
|
class OIDCRequest with EquatableMixin {
|
||||||
|
|
||||||
final String baseUrl;
|
final String baseUrl;
|
||||||
final String resourceUrl = 'https://gateway.upn.integration-open-paas.org';
|
final String resourceUrl;
|
||||||
final String relUrl = 'http://openid.net/specs/connect/1.0/issuer';
|
static const String relUrl = 'http://openid.net/specs/connect/1.0/issuer';
|
||||||
|
|
||||||
OIDCRequest({required this.baseUrl});
|
OIDCRequest({required this.baseUrl, required this.resourceUrl});
|
||||||
|
|
||||||
@override
|
@override
|
||||||
List<Object?> get props => [baseUrl, resourceUrl, relUrl];
|
List<Object?> get props => [baseUrl, resourceUrl, relUrl];
|
||||||
|
|||||||
Reference in New Issue
Block a user