| // Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file |
| // for details. All rights reserved. Use of this source code is governed by a |
| // BSD-style license that can be found in the LICENSE file. |
| |
| import 'dart:async'; |
| |
| import 'package:http/browser_client.dart'; |
| import 'package:http/http.dart'; |
| |
| import 'src/auth_functions.dart'; |
| import 'src/auth_http_utils.dart'; |
| import 'src/http_client_base.dart'; |
| import 'src/oauth2_flows/implicit.dart'; |
| import 'src/oauth2_flows/token_model.dart'; |
| import 'src/service_account_credentials.dart'; |
| |
| export 'googleapis_auth.dart'; |
| export 'src/authentication_exception.dart' show AuthenticationException; |
| export 'src/oauth2_flows/token_model.dart' |
| show |
| CodeResponse, |
| requestAccessCredentials, |
| requestAuthorizationCode, |
| revokeConsent; |
| |
| /// Will create and complete with a [BrowserOAuth2Flow] object. |
| /// |
| /// {@template googleapis_auth_gis_deprecated} |
| /// This member is *deprecated*. Use [requestAccessCredentials] or |
| /// [requestAuthorizationCode] instead. |
| /// {@endtemplate} |
| /// |
| /// This function will perform an implicit browser based oauth2 flow. |
| /// |
| /// It will load Google's `gapi` library and initialize it. After initialization |
| /// it will complete with a [BrowserOAuth2Flow] object. The flow object can be |
| /// used to obtain `AccessCredentials` or an authenticated HTTP client. |
| /// |
| /// If loading or initializing the `gapi` library results in an error, this |
| /// future will complete with an error. |
| /// |
| /// {@macro googleapis_auth_clientId_param} |
| /// |
| /// {@template googleapis_auth_baseClient_param} |
| /// If [baseClient] is provided, all HTTP requests will be made with it. |
| /// Otherwise, a new [Client] instance will be created. |
| /// {@endtemplate} |
| /// |
| /// {@macro googleapis_auth_close_the_client} |
| /// {@macro googleapis_auth_not_close_the_baseClient} |
| @Deprecated( |
| 'This member is deprecated. Use requestAccessCredentials or ' |
| 'requestAuthorizationCode instead.', |
| ) |
| Future<BrowserOAuth2Flow> createImplicitBrowserFlow( |
| ClientId clientId, |
| List<String> scopes, { |
| Client? baseClient, |
| @Deprecated( |
| 'Undocumented feature. May help debugging. ' |
| 'Do not include in production code.', |
| ) |
| bool enableDebugLogs = false, |
| }) async { |
| final refCountedClient = baseClient == null |
| ? RefCountedClient(BrowserClient()) |
| : RefCountedClient(baseClient, initialRefCount: 2); |
| |
| final flow = ImplicitFlow(clientId.identifier, scopes, enableDebugLogs); |
| |
| try { |
| await flow.initialize(); |
| } catch (_) { |
| refCountedClient.close(); |
| rethrow; |
| } |
| return BrowserOAuth2Flow._(flow, refCountedClient); |
| } |
| |
| /// Used for obtaining oauth2 access credentials. |
| /// |
| /// {@macro googleapis_auth_gis_deprecated} |
| /// |
| /// Warning: |
| /// |
| /// The methods [obtainAccessCredentialsViaUserConsent] and |
| /// [clientViaUserConsent] try to open a popup window for the user authorization |
| /// dialog. |
| /// |
| /// In order to prevent browsers from blocking the popup window, these |
| /// methods should only be called inside an event handler, since most |
| /// browsers do not block popup windows created in response to a user |
| /// interaction. |
| @Deprecated( |
| 'This member is deprecated. Use requestAccessCredentials or ' |
| 'requestAuthorizationCode instead.', |
| ) |
| class BrowserOAuth2Flow { |
| final ImplicitFlow _flow; |
| final RefCountedClient _client; |
| |
| bool _wasClosed = false; |
| |
| /// The HTTP client passed in will be closed if `close` was called and all |
| /// generated HTTP clients via [clientViaUserConsent] were closed. |
| BrowserOAuth2Flow._(this._flow, this._client); |
| |
| /// Obtain oauth2 [AccessCredentials]. |
| /// |
| /// {@template googleapis_auth_force} |
| /// If [force] is `true` this will create a popup window and ask the user to |
| /// grant the application offline access. In case the user is not already |
| /// logged in, they will be presented with an login dialog first. |
| /// |
| /// If [force] is `false` this will only create a popup window if the user |
| /// has not already granted the application access. |
| /// {@endtemplate} |
| /// |
| /// {@template googleapis_auth_immediate} |
| /// If [immediate] is `true` there will be no user involvement. If the user |
| /// is either not logged in or has not already granted the application access, |
| /// a `UserConsentException` will be thrown. |
| /// |
| /// If [immediate] is `false` the user might be asked to login (if not |
| /// already logged in) and might get asked to grant the application access |
| /// (if the application hasn't been granted access before). |
| /// {@endtemplate} |
| /// |
| /// {@template googleapis_auth_loginHint} |
| /// If [loginHint] is not `null`, it will be passed to the server as a hint |
| /// to which user is being signed-in. This can e.g. be an email or a User ID |
| /// which might be used as pre-selection in the sign-in flow. |
| /// {@endtemplate} |
| /// |
| /// {@macro googleapis_auth_hostedDomain_param} |
| /// |
| /// If [responseTypes] is not `null` or empty, it will be sent to the server |
| /// to inform the server of the type of responses to reply with. |
| /// |
| /// {@template googleapis_auth_user_consent_return} |
| /// The returned [Future] will complete with [AccessCredentials] if the user |
| /// has given the application access to their data. |
| /// Otherwise, a [UserConsentException] will be thrown. |
| /// {@endtemplate} |
| /// |
| /// {@macro googleapis_auth_hostedDomain_param} |
| Future<AccessCredentials> obtainAccessCredentialsViaUserConsent({ |
| bool force = false, |
| bool immediate = false, |
| String? loginHint, |
| List<ResponseType>? responseTypes, |
| String? hostedDomain, |
| }) { |
| _ensureOpen(); |
| return _flow.login( |
| prompt: _promptFromBooleans(force, immediate), |
| loginHint: loginHint, |
| responseTypes: responseTypes, |
| hostedDomain: hostedDomain, |
| ); |
| } |
| |
| /// Obtains [AccessCredentials] and returns an authenticated HTTP client. |
| /// |
| /// {@template googleapis_auth_returned_auto_refresh_client} |
| /// HTTP requests made on the returned client will get an additional |
| /// `Authorization` header with the [AccessCredentials] obtained. |
| /// Once the [AccessCredentials] expire, it will use it's refresh token |
| /// (if available) to obtain new credentials. |
| /// See [autoRefreshingClient] for more information. |
| /// {@endtemplate} |
| /// |
| /// See [obtainAccessCredentialsViaUserConsent] for how credentials will be |
| /// obtained. Errors from [obtainAccessCredentialsViaUserConsent] will be let |
| /// through to the returned `Future` of this function and to the returned |
| /// HTTP client (in case of credential refreshes). |
| /// |
| /// The returned HTTP client will forward errors from lower levels via it's |
| /// `Future<Response>` or it's `Response.read()` stream. |
| /// |
| /// {@macro googleapis_auth_immediate} |
| /// |
| /// {@macro googleapis_auth_close_the_client} |
| /// |
| /// {@macro googleapis_auth_loginHint} |
| /// |
| /// {@macro googleapis_auth_hostedDomain_param} |
| Future<AutoRefreshingAuthClient> clientViaUserConsent({ |
| bool immediate = false, |
| String? loginHint, |
| String? hostedDomain, |
| }) async { |
| final credentials = await obtainAccessCredentialsViaUserConsent( |
| immediate: immediate, |
| loginHint: loginHint, |
| hostedDomain: hostedDomain, |
| ); |
| return _clientFromCredentials(credentials); |
| } |
| |
| /// Obtains [AccessCredentials] and an authorization code which can be |
| /// exchanged for permanent access credentials. |
| /// |
| /// Use case: |
| /// A web application might want to get consent for accessing data on behalf |
| /// of a user. The client part is a dynamic webapp which wants to open a |
| /// popup which asks the user for consent. The webapp might want to use the |
| /// credentials to make API calls, but the server may want to have offline |
| /// access to user data as well. |
| /// |
| /// {@macro googleapis_auth_force} |
| /// |
| /// {@macro googleapis_auth_immediate} |
| /// |
| /// {@macro googleapis_auth_loginHint} |
| /// |
| /// {@macro googleapis_auth_hostedDomain_param} |
| Future<HybridFlowResult> runHybridFlow({ |
| bool force = true, |
| bool immediate = false, |
| String? loginHint, |
| String? hostedDomain, |
| }) async { |
| _ensureOpen(); |
| final result = await _flow.loginHybrid( |
| prompt: _promptFromBooleans(force, immediate), |
| hostedDomain: hostedDomain, |
| loginHint: loginHint, |
| ); |
| return HybridFlowResult(this, result.credential, result.code); |
| } |
| |
| /// Will close this [BrowserOAuth2Flow] object and the HTTP [Client] it is |
| /// using. |
| /// |
| /// The clients obtained via [clientViaUserConsent] will continue to work. |
| /// The client obtained via `newClient` of obtained [HybridFlowResult] objects |
| /// will continue to work. |
| /// |
| /// After this flow object and all obtained clients were closed the underlying |
| /// HTTP client will be closed as well. |
| /// |
| /// After calling this `close` method, calls to [clientViaUserConsent], |
| /// [obtainAccessCredentialsViaUserConsent] and to `newClient` on returned |
| /// [HybridFlowResult] objects will fail. |
| void close() { |
| _ensureOpen(); |
| _wasClosed = true; |
| _client.close(); |
| } |
| |
| void _ensureOpen() { |
| if (_wasClosed) { |
| throw StateError('BrowserOAuth2Flow has already been closed.'); |
| } |
| } |
| |
| AutoRefreshingAuthClient _clientFromCredentials(AccessCredentials cred) { |
| _ensureOpen(); |
| _client.acquire(); |
| return _AutoRefreshingBrowserClient(_client, cred, _flow); |
| } |
| } |
| |
| /// Represents the result of running a browser based hybrid flow. |
| /// |
| /// {@macro googleapis_auth_gis_deprecated} |
| /// |
| /// The `credentials` field holds credentials which can be used on the client |
| /// side. The `newClient` function can be used to make a new authenticated HTTP |
| /// client using these credentials. |
| /// |
| /// The `authorizationCode` can be sent to the server, which knows the |
| /// "client secret" and can exchange it with long-lived access credentials. |
| /// |
| /// See the `obtainAccessCredentialsViaCodeExchange` function in the |
| /// `googleapis_auth.auth_io` library for more details on how to use the |
| /// authorization code. |
| @Deprecated( |
| 'This member is deprecated. Use requestAccessCredentials or ' |
| 'requestAuthorizationCode instead.', |
| ) |
| class HybridFlowResult { |
| final BrowserOAuth2Flow _flow; |
| |
| /// Access credentials for making authenticated HTTP requests. |
| final AccessCredentials credentials; |
| |
| /// The authorization code received from the authorization endpoint. |
| /// |
| /// The auth code can be used to receive permanent access credentials. |
| /// This requires a confidential client which can keep a secret. |
| final String? authorizationCode; |
| |
| HybridFlowResult(this._flow, this.credentials, this.authorizationCode); |
| |
| AutoRefreshingAuthClient newClient() { |
| _flow._ensureOpen(); |
| return _flow._clientFromCredentials(credentials); |
| } |
| } |
| |
| class _AutoRefreshingBrowserClient extends AutoRefreshDelegatingClient { |
| @override |
| AccessCredentials credentials; |
| final ImplicitFlow _flow; |
| Client _authClient; |
| |
| _AutoRefreshingBrowserClient(super.client, this.credentials, this._flow) |
| : _authClient = authenticatedClient(client, credentials); |
| |
| @override |
| Future<StreamedResponse> send(BaseRequest request) async { |
| if (!credentials.accessToken.hasExpired) { |
| return _authClient.send(request); |
| } |
| credentials = await _flow.login(prompt: 'none'); |
| notifyAboutNewCredentials(credentials); |
| _authClient = authenticatedClient(baseClient, credentials); |
| return _authClient.send(request); |
| } |
| } |
| |
| String? _promptFromBooleans(bool force, bool immediate) { |
| if (force) { |
| if (immediate) { |
| throw ArgumentError.value( |
| immediate, |
| 'immediate', |
| 'Cannot be true if `force` is also true.', |
| ); |
| } |
| return 'consent'; |
| } |
| if (immediate) { |
| return 'none'; |
| } |
| return null; |
| } |