Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

✨ Added more oauth2 package parameters #25

Merged
merged 1 commit into from
Sep 10, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
11 changes: 11 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
@@ -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`
Expand Down
45 changes: 39 additions & 6 deletions lib/src/oauth_chopper.dart
Original file line number Diff line number Diff line change
@@ -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';
Expand All @@ -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].
Expand All @@ -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
Expand All @@ -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<String>? 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<String, dynamic> Function(MediaType? contentType, String body)?
getParameters;

/// Get stored [OAuthToken].
Future<OAuthToken?> get token async {
final credentialsJson = await _storage.fetchCredentials();
Expand All @@ -78,15 +100,22 @@ 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<OAuthToken?> refresh() async {
///
/// See [oauth2.Credentials.refresh] for more information
/// and information about [newScopes] and [basicAuth].
Future<OAuthToken?> refresh({
bool basicAuth = true,
Iterable<String>? newScopes,
}) async {
final credentialsJson = await _storage.fetchCredentials();
if (credentialsJson == null) return null;
final credentials = oauth2.Credentials.fromJson(credentialsJson);
try {
final newCredentials = await credentials.refresh(
identifier: identifier,
secret: secret,
newScopes: newScopes,
basicAuth: basicAuth,
httpClient: httpClient,
);
await _storage.saveCredentials(newCredentials.toJson());
Expand All @@ -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);
Expand Down
92 changes: 72 additions & 20 deletions lib/src/oauth_grant.dart
Original file line number Diff line number Diff line change
@@ -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.
Expand All @@ -20,10 +22,15 @@ abstract interface class OAuthGrant {
/// Obtains credentials from an authorization server.
Future<String> handle(
Uri authorizationEndpoint,
String identifier,
String secret,
String identifier, {
String? secret,
http.Client? httpClient,
);
Iterable<String>? scopes,
bool basicAuth = true,
String? delimiter,
Map<String, dynamic> Function(MediaType? contentType, String body)?
getParameters,
});
}

/// {@template resource_owner_password_grant}
Expand All @@ -37,6 +44,7 @@ class ResourceOwnerPasswordGrant implements OAuthGrant {
const ResourceOwnerPasswordGrant({
required this.username,
required this.password,
this.onCredentialsRefreshed,
});

/// Username used for obtaining credentials.
Expand All @@ -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<String> handle(
Uri authorizationEndpoint,
String identifier,
String secret,
String identifier, {
String? secret,
http.Client? httpClient,
) async {
final client = await oauth.resourceOwnerPasswordGrant(
Iterable<String>? scopes,
bool basicAuth = true,
String? delimiter,
Map<String, dynamic> 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();
}
Expand All @@ -74,15 +98,24 @@ class ClientCredentialsGrant implements OAuthGrant {
@override
Future<String> handle(
Uri authorizationEndpoint,
String identifier,
String secret,
String identifier, {
String? secret,
http.Client? httpClient,
) async {
final client = await oauth.clientCredentialsGrant(
Iterable<String>? scopes,
bool basicAuth = true,
String? delimiter,
Map<String, dynamic> 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();
}
Expand All @@ -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
Expand All @@ -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<String> 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.
Expand All @@ -125,15 +166,26 @@ class AuthorizationCodeGrant implements OAuthGrant {
@override
Future<String> handle(
Uri authorizationEndpoint,
String identifier,
String secret,
String identifier, {
String? secret,
http.Client? httpClient,
) async {
final grant = oauth.AuthorizationCodeGrant(
Iterable<String>? scopes,
bool basicAuth = true,
String? delimiter,
Map<String, dynamic> 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(
Expand Down
5 changes: 3 additions & 2 deletions pubspec.yaml
Original file line number Diff line number Diff line change
@@ -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:
Expand All @@ -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
Expand Down
16 changes: 13 additions & 3 deletions test/oauth_chopper_test.dart
Original file line number Diff line number Diff line change
Expand Up @@ -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',
Expand All @@ -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');
Expand Down