LCOV - code coverage report
Current view: top level - lib/network/authentication - apptive_grid_authenticator.dart (source / functions) Hit Total Coverage
Test: lcov.info Lines: 98 99 99.0 %
Date: 2021-11-15 14:58:17 Functions: 0 0 -

          Line data    Source code
       1             : part of apptive_grid_network;
       2             : 
       3             : /// Class for handling authentication related methods for ApptiveGrid
       4             : class ApptiveGridAuthenticator {
       5             :   /// Create a new [ApptiveGridAuthenticator] for [apptiveGridClient]
       6           3 :   ApptiveGridAuthenticator({
       7             :     this.options = const ApptiveGridOptions(),
       8             :     this.httpClient,
       9             :   }) {
      10             :     if (!kIsWeb) {
      11           6 :       _authCallbackSubscription = uni_links.uriLinkStream
      12           3 :           .where(
      13           1 :             (event) =>
      14             :                 event != null &&
      15           2 :                 event.scheme ==
      16           4 :                     options.authenticationOptions.redirectScheme?.toLowerCase(),
      17             :           )
      18           5 :           .listen((event) => _handleAuthRedirect(event!));
      19             :     }
      20             : 
      21           9 :     if (options.authenticationOptions.persistCredentials) {
      22           1 :       _authenticationStorage = const FlutterSecureStorageCredentialStorage();
      23             :     }
      24             :   }
      25             : 
      26             :   /// Creates an [ApptiveGridAuthenticator] with a specific [AuthenticationStorage]
      27           1 :   @visibleForTesting
      28             :   ApptiveGridAuthenticator.withAuthenticationStorage({
      29             :     this.options = const ApptiveGridOptions(),
      30             :     this.httpClient,
      31             :     required AuthenticationStorage? storage,
      32             :   })  : _authenticationStorage = storage,
      33             :         _authCallbackSubscription = null;
      34             : 
      35             :   /// [ApptiveGridOptions] used for getting the correct [ApptiveGridEnvironment.authRealm]
      36             :   /// and checking if authentication should automatically be handled
      37             :   ApptiveGridOptions options;
      38             : 
      39           2 :   Uri get _uri => Uri.parse(
      40           4 :         'https://iam.zweidenker.de/auth/realms/${options.environment.authRealm}',
      41             :       );
      42             : 
      43             :   /// Http Client that should be used for Auth Requests
      44             :   final http.Client? httpClient;
      45             : 
      46             :   Client? _authClient;
      47             : 
      48             :   TokenResponse? _token;
      49             :   Credential? _credential;
      50             : 
      51             :   AuthenticationStorage? _authenticationStorage;
      52             : 
      53             :   /// Override the token for testing purposes
      54           2 :   @visibleForTesting
      55           2 :   void setToken(TokenResponse? token) => _token = token;
      56             : 
      57             :   /// Override the Credential for testing purposes
      58           2 :   @visibleForTesting
      59             :   void setCredential(Credential? credential) {
      60           3 :     _authenticationStorage?.saveCredential(
      61           2 :       credential != null ? jsonEncode(credential.toJson()) : null,
      62             :     );
      63           2 :     _credential = credential;
      64             :   }
      65             : 
      66             :   /// Override the [Client] for testing purposes
      67           1 :   @visibleForTesting
      68           1 :   void setAuthClient(Client client) => _authClient = client;
      69             : 
      70             :   /// Override the [Authenticator] for testing purposes
      71             :   @visibleForTesting
      72             :   Authenticator? testAuthenticator;
      73             : 
      74             :   late final StreamSubscription<Uri?>? _authCallbackSubscription;
      75             : 
      76           1 :   Future<Client> get _client async {
      77           1 :     Future<Client> createClient() async {
      78           4 :       final issuer = await Issuer.discover(_uri, httpClient: httpClient);
      79           2 :       return Client(issuer, 'app', httpClient: httpClient, clientSecret: '');
      80             :     }
      81             : 
      82           2 :     return _authClient ??= await createClient();
      83             :   }
      84             : 
      85             :   /// Used to test implementation of get _client
      86           1 :   @visibleForTesting
      87           1 :   Future<Client> get authClient => _client;
      88             : 
      89             :   /// Open the Authentication Webpage
      90             :   ///
      91             :   /// Returns [Credential] from the authentication call
      92           1 :   Future<Credential?> authenticate() async {
      93           2 :     final client = await _client;
      94             : 
      95           1 :     final authenticator = testAuthenticator ??
      96           1 :         Authenticator(
      97             :           client,
      98           1 :           scopes: [],
      99           1 :           urlLauncher: _launchUrl,
     100           3 :           redirectUri: options.authenticationOptions.redirectScheme != null
     101           1 :               ? Uri(
     102           3 :                   scheme: options.authenticationOptions.redirectScheme,
     103           5 :                   host: Uri.parse(options.environment.url).host,
     104             :                 )
     105             :               : null,
     106             :         );
     107           3 :     setCredential(await authenticator.authorize());
     108             : 
     109           4 :     setToken(await _credential?.getTokenResponse());
     110             : 
     111             :     try {
     112           2 :       await closeWebView();
     113           1 :     } on MissingPluginException {
     114           1 :       debugPrint('closeWebView is not available on this platform');
     115           1 :     } on UnimplementedError {
     116           1 :       debugPrint('closeWebView is not available on this platform');
     117             :     }
     118             : 
     119           1 :     return _credential;
     120             :   }
     121             : 
     122           1 :   Future<void> _handleAuthRedirect(Uri uri) async {
     123           2 :     final client = await _client;
     124           1 :     client.createCredential(
     125           1 :       refreshToken: _token?.refreshToken,
     126             :     );
     127           1 :     final authenticator = testAuthenticator ??
     128           1 :         Authenticator(
     129             :           client, // coverage:ignore-line
     130           3 :           redirectUri: options.authenticationOptions.redirectScheme != null
     131           1 :               ? Uri(
     132           3 :                   scheme: options.authenticationOptions.redirectScheme,
     133           5 :                   host: Uri.parse(options.environment.url).host,
     134             :                 )
     135             :               : null,
     136           1 :           urlLauncher: _launchUrl,
     137             :         );
     138             : 
     139           3 :     await authenticator.processResult(uri.queryParameters);
     140             :   }
     141             : 
     142             :   /// Dispose any resources in the Authenticator
     143           3 :   void dispose() {
     144           6 :     _authCallbackSubscription?.cancel();
     145             :   }
     146             : 
     147             :   /// Checks the authentication status and performs actions depending on the status
     148             :   ///
     149             :   /// If there is a [ApptiveGridAuthenticationOptions.apiKey] is set in [options] this will return without any Action
     150             :   ///
     151             :   /// If the User is not authenticated and [ApptiveGridAuthenticationOptions.autoAuthenticate] is true this will call [authenticate]
     152             :   ///
     153             :   /// If the token is expired it will refresh the token using the refresh token
     154           2 :   Future<void> checkAuthentication() async {
     155           2 :     if (_token == null) {
     156           4 :       await Future.value(
     157           3 :         _authenticationStorage?.credential,
     158           4 :       ).then((credentialString) async {
     159           2 :         final jsonCredential = jsonDecode(credentialString ?? 'null');
     160             :         if (jsonCredential != null) {
     161           1 :           final credential = Credential.fromJson(
     162             :             jsonCredential,
     163           1 :             httpClient: httpClient,
     164             :           );
     165           1 :           setCredential(credential);
     166             :           try {
     167           2 :             final token = await credential.getTokenResponse(true);
     168           1 :             setToken(token);
     169             :             return;
     170           1 :           } on OpenIdException catch (_) {
     171           1 :             setCredential(null);
     172           1 :             debugPrint('Could not refresh saved token');
     173             :           }
     174             :         }
     175           6 :         if (options.authenticationOptions.apiKey != null) {
     176             :           // User has ApiKey provided
     177             :           return;
     178           6 :         } else if (options.authenticationOptions.autoAuthenticate) {
     179           2 :           await authenticate();
     180             :         }
     181             :       });
     182           6 :     } else if ((_token?.expiresAt?.difference(DateTime.now()).inSeconds ?? 0) <
     183             :         70) {
     184           4 :       setToken(await _credential?.getTokenResponse(true));
     185             :     }
     186             :   }
     187             : 
     188             :   /// Performs a call to Logout the User
     189             :   ///
     190             :   /// even if the Call Fails the token and credential will be cleared
     191           2 :   Future<http.Response?> logout() async {
     192           3 :     final logoutUrl = _credential?.generateLogoutUrl();
     193             :     http.Response? response;
     194             :     if (logoutUrl != null) {
     195           3 :       response = await (httpClient ?? http.Client()).get(
     196             :         logoutUrl,
     197           1 :         headers: {
     198           1 :           HttpHeaders.authorizationHeader: header!,
     199             :         },
     200             :       );
     201             :     }
     202           2 :     setToken(null);
     203           2 :     setCredential(null);
     204           2 :     _authClient = null;
     205             : 
     206             :     return response;
     207             :   }
     208             : 
     209             :   /// If there is a authenticated User this will return the authentication header
     210             :   ///
     211             :   /// User Authentication is prioritized over ApiKey Authentication
     212           2 :   String? get header {
     213           2 :     if (_token != null) {
     214           1 :       final token = _token!;
     215           3 :       return '${token.tokenType} ${token.accessToken}';
     216             :     }
     217           6 :     if (options.authenticationOptions.apiKey != null) {
     218           3 :       final apiKey = options.authenticationOptions.apiKey!;
     219           6 :       return 'Basic ${base64Encode(utf8.encode('${apiKey.authKey}:${apiKey.password}'))}';
     220             :     }
     221             :   }
     222             : 
     223           1 :   Future<void> _launchUrl(String url) async {
     224           2 :     if (await canLaunch(url)) {
     225             :       try {
     226           2 :         await launch(url);
     227           0 :       } on PlatformException {
     228             :         // Could not launch Url
     229             :       }
     230             :     }
     231             :   }
     232             : 
     233             :   /// Checks if the User is Authenticated
     234           1 :   bool get isAuthenticated =>
     235           4 :       options.authenticationOptions.apiKey != null || _token != null;
     236             : }
     237             : 
     238             : /// Interface to provide common functionality for authorization operations
     239             : abstract class IAuthenticator {
     240             :   /// Authorizes the User against the Auth Server
     241             :   Future<Credential?> authorize();
     242             : }

Generated by: LCOV version 1.15