diff --git a/CHANGELOG.md b/CHANGELOG.md index a52b7c0..461989f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,3 +1,14 @@ +## 1.1.0 +- Synced oauth_chopper with auth2 package. This makes more parameters available which are supported by oauth2. + - Be default `OAuthChopper` client can now also be provided with the following parameter. Which will be passed to oauth2. + - `scopes` + - `basicAuth` + - `delimiter` + - `getParameters` + - Added `newScopes` & `basicAuth` parameters to `OAuthChopper.refresh` which wil be passed to oauth2 + - BREAKING: `scopes` has been removed from `AuthorizationCodeGrant`. These are now provided in the `OAuthChopper` client. + - BREAKING: `OAuthGrant.handle` has been extended to support new parameters as optional named parameters, `including` secret and `httpClient`. + ## 1.0.1 - Updated dependencies: - `sdk` to `>=3.4.0 <4.0.0` diff --git a/lib/src/oauth_chopper.dart b/lib/src/oauth_chopper.dart index db15c14..f3f24f6 100644 --- a/lib/src/oauth_chopper.dart +++ b/lib/src/oauth_chopper.dart @@ -1,6 +1,7 @@ import 'dart:async'; import 'package:http/http.dart' as http; +import 'package:http_parser/http_parser.dart'; import 'package:oauth2/oauth2.dart' as oauth2; import 'package:oauth_chopper/src/oauth_grant.dart'; import 'package:oauth_chopper/src/oauth_interceptor.dart'; @@ -25,9 +26,13 @@ class OAuthChopper { OAuthChopper({ required this.authorizationEndpoint, required this.identifier, - required this.secret, + this.secret, this.endSessionEndpoint, this.httpClient, + this.scopes, + this.basicAuth = true, + this.delimiter, + this.getParameters, /// OAuth storage for storing credentials. /// By default it will use a in memory storage [MemoryStorage]. @@ -46,7 +51,7 @@ class OAuthChopper { final String identifier; /// OAuth secret. - final String secret; + final String? secret; /// OAuth storage for storing credentials. /// By default it will use a in memory storage. For persisting the credentials @@ -58,6 +63,23 @@ class OAuthChopper { /// for making new requests. final http.Client? httpClient; + /// The scopes that the client is requesting access to. + /// Will be passed to [oauth2]. + final Iterable? scopes; + + /// Whether to use HTTP Basic authentication for authorizing the client. + /// Will be passed to [oauth2]. + final bool basicAuth; + + /// A [String] used to separate scopes; defaults to `" "`. + /// Will be passed to [oauth2]. + final String? delimiter; + + /// The function used to parse parameters from a host's response. + /// Will be passed to [oauth2]. + final Map Function(MediaType? contentType, String body)? + getParameters; + /// Get stored [OAuthToken]. Future get token async { final credentialsJson = await _storage.fetchCredentials(); @@ -78,8 +100,13 @@ class OAuthChopper { /// instance. /// Throws an exception when refreshing fails. If the exception is a /// [oauth2.AuthorizationException] it clears the storage. - /// See [oauth2.Credentials.refresh] - Future refresh() async { + /// + /// See [oauth2.Credentials.refresh] for more information + /// and information about [newScopes] and [basicAuth]. + Future refresh({ + bool basicAuth = true, + Iterable? newScopes, + }) async { final credentialsJson = await _storage.fetchCredentials(); if (credentialsJson == null) return null; final credentials = oauth2.Credentials.fromJson(credentialsJson); @@ -87,6 +114,8 @@ class OAuthChopper { final newCredentials = await credentials.refresh( identifier: identifier, secret: secret, + newScopes: newScopes, + basicAuth: basicAuth, httpClient: httpClient, ); await _storage.saveCredentials(newCredentials.toJson()); @@ -109,8 +138,12 @@ class OAuthChopper { final credentials = await grant.handle( authorizationEndpoint, identifier, - secret, - httpClient, + secret: secret, + httpClient: httpClient, + scopes: scopes, + getParameters: getParameters, + delimiter: delimiter, + basicAuth: basicAuth, ); await _storage.saveCredentials(credentials); diff --git a/lib/src/oauth_grant.dart b/lib/src/oauth_grant.dart index 6789b7c..26e5ccc 100644 --- a/lib/src/oauth_grant.dart +++ b/lib/src/oauth_grant.dart @@ -1,5 +1,7 @@ import 'package:http/http.dart' as http; -import 'package:oauth2/oauth2.dart' as oauth; +import 'package:http_parser/http_parser.dart'; +import 'package:oauth2/oauth2.dart' as oauth2; +import 'package:oauth2/oauth2.dart'; /// {@template oauth_grant} /// Interface for a OAuth grant. @@ -20,10 +22,15 @@ abstract interface class OAuthGrant { /// Obtains credentials from an authorization server. Future handle( Uri authorizationEndpoint, - String identifier, - String secret, + String identifier, { + String? secret, http.Client? httpClient, - ); + Iterable? scopes, + bool basicAuth = true, + String? delimiter, + Map Function(MediaType? contentType, String body)? + getParameters, + }); } /// {@template resource_owner_password_grant} @@ -37,6 +44,7 @@ class ResourceOwnerPasswordGrant implements OAuthGrant { const ResourceOwnerPasswordGrant({ required this.username, required this.password, + this.onCredentialsRefreshed, }); /// Username used for obtaining credentials. @@ -45,20 +53,36 @@ class ResourceOwnerPasswordGrant implements OAuthGrant { /// Password used for obtaining credentials. final String password; + /// Callback to be invoked whenever the credentials are refreshed. + /// + /// This will be passed as-is to the constructed [Client]. + /// Will be passed to [oauth2]. + final CredentialsRefreshedCallback? onCredentialsRefreshed; + @override Future handle( Uri authorizationEndpoint, - String identifier, - String secret, + String identifier, { + String? secret, http.Client? httpClient, - ) async { - final client = await oauth.resourceOwnerPasswordGrant( + Iterable? scopes, + bool basicAuth = true, + String? delimiter, + Map Function(MediaType? contentType, String body)? + getParameters, + }) async { + final client = await oauth2.resourceOwnerPasswordGrant( authorizationEndpoint, username, password, secret: secret, identifier: identifier, + scopes: scopes, + basicAuth: basicAuth, + delimiter: delimiter, httpClient: httpClient, + getParameters: getParameters, + onCredentialsRefreshed: onCredentialsRefreshed, ); return client.credentials.toJson(); } @@ -74,15 +98,24 @@ class ClientCredentialsGrant implements OAuthGrant { @override Future handle( Uri authorizationEndpoint, - String identifier, - String secret, + String identifier, { + String? secret, http.Client? httpClient, - ) async { - final client = await oauth.clientCredentialsGrant( + Iterable? scopes, + bool basicAuth = true, + String? delimiter, + Map Function(MediaType? contentType, String body)? + getParameters, + }) async { + final client = await oauth2.clientCredentialsGrant( authorizationEndpoint, identifier, secret, + scopes: scopes, + basicAuth: basicAuth, + delimiter: delimiter, httpClient: httpClient, + getParameters: getParameters, ); return client.credentials.toJson(); } @@ -95,10 +128,11 @@ class AuthorizationCodeGrant implements OAuthGrant { /// {@macro authorization_code_grant} const AuthorizationCodeGrant({ required this.tokenEndpoint, - required this.scopes, required this.redirectUrl, required this.redirect, required this.listen, + this.onCredentialsRefreshed, + this.codeVerifier, }); /// A URL provided by the authorization server that this library uses to @@ -111,9 +145,16 @@ class AuthorizationCodeGrant implements OAuthGrant { /// The redirect URL where the resource owner will redirect to. final Uri redirectUrl; - /// The specific permissions being requested from the authorization server may - /// be specified via [scopes]. - final List scopes; + /// Callback to be invoked whenever the credentials are refreshed. + /// + /// This will be passed as-is to the constructed [Client]. + /// Will be passed to [oauth2]. + final CredentialsRefreshedCallback? onCredentialsRefreshed; + + /// The PKCE code verifier. Will be generated if one is not provided in the + /// constructor. + /// Will be passed to [oauth2]. + final String? codeVerifier; /// Callback used for redirect the authorizationUrl given by the authorization /// server. @@ -125,15 +166,26 @@ class AuthorizationCodeGrant implements OAuthGrant { @override Future handle( Uri authorizationEndpoint, - String identifier, - String secret, + String identifier, { + String? secret, http.Client? httpClient, - ) async { - final grant = oauth.AuthorizationCodeGrant( + Iterable? scopes, + bool basicAuth = true, + String? delimiter, + Map Function(MediaType? contentType, String body)? + getParameters, + }) async { + final grant = oauth2.AuthorizationCodeGrant( identifier, authorizationEndpoint, tokenEndpoint, + basicAuth: basicAuth, + delimiter: delimiter, + getParameters: getParameters, + secret: secret, httpClient: httpClient, + onCredentialsRefreshed: onCredentialsRefreshed, + codeVerifier: codeVerifier, ); final authorizationUrl = grant.getAuthorizationUrl( diff --git a/pubspec.yaml b/pubspec.yaml index 4e1a1ea..437ec08 100644 --- a/pubspec.yaml +++ b/pubspec.yaml @@ -1,6 +1,6 @@ name: oauth_chopper description: Add and manage OAuth2 authentication for your Chopper client. -version: 1.0.1 +version: 1.1.0 homepage: https://github.com/DutchCodingCompany/oauth_chopper environment: @@ -9,7 +9,8 @@ environment: dependencies: chopper: ^8.0.1+1 http: ^1.2.2 - oauth2: ^2.0.2 + http_parser: ^4.1.0 + oauth2: ^2.0.3 dev_dependencies: mocktail: ^1.0.4 diff --git a/test/oauth_chopper_test.dart b/test/oauth_chopper_test.dart index f456727..9bb180b 100644 --- a/test/oauth_chopper_test.dart +++ b/test/oauth_chopper_test.dart @@ -78,8 +78,18 @@ void main() { test('Successful grant is stored', () async { // arrange when(() => storageMock.saveCredentials(any())).thenAnswer((_) => null); - when(() => grantMock.handle(any(), any(), any(), null)) - .thenAnswer((_) async => testJson); + when( + () => grantMock.handle( + any(), + any(), + secret: any(named: 'secret'), + basicAuth: any(named: 'basicAuth'), + httpClient: any(named: 'httpClient'), + delimiter: any(named: 'delimiter'), + getParameters: any(named: 'getParameters'), + scopes: any(named: 'scopes'), + ), + ).thenAnswer((_) async => testJson); final oauthChopper = OAuthChopper( authorizationEndpoint: Uri.parse('endpoint'), identifier: 'identifier', @@ -91,7 +101,7 @@ void main() { final token = await oauthChopper.requestGrant(grantMock); // assert - verify(() => grantMock.handle(any(), 'identifier', 'secret', null)) + verify(() => grantMock.handle(any(), 'identifier', secret: 'secret')) .called(1); verify(() => storageMock.saveCredentials(testJson)).called(1); expect(token.accessToken, 'accesToken');