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

Generated by: LCOV version 1.15