Skip to content

Commit

Permalink
Merge pull request #25 from DutchCodingCompany/feature/mirror_oauth_lib
Browse files Browse the repository at this point in the history
✨ Added more oauth2 package parameters
  • Loading branch information
Guldem authored Sep 10, 2024
2 parents f593462 + bf106d6 commit 3ce6a7d
Show file tree
Hide file tree
Showing 5 changed files with 138 additions and 31 deletions.
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

0 comments on commit 3ce6a7d

Please sign in to comment.