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