Skip to content

Commit

Permalink
Merge pull request #17 from DutchCodingCompany/feature/add_very_good_…
Browse files Browse the repository at this point in the history
…analysis

✨ Add very good analysis
  • Loading branch information
Guldem authored Mar 15, 2024
2 parents 76a0513 + 8232eea commit 4162fe5
Show file tree
Hide file tree
Showing 16 changed files with 263 additions and 140 deletions.
44 changes: 14 additions & 30 deletions analysis_options.yaml
Original file line number Diff line number Diff line change
@@ -1,30 +1,14 @@
# This file configures the static analysis results for your project (errors,
# warnings, and lints).
#
# This enables the 'recommended' set of lints from `package:lints`.
# This set helps identify many issues that may lead to problems when running
# or consuming Dart code, and enforces writing Dart using a single, idiomatic
# style and format.
#
# If you want a smaller set of lints you can change this to specify
# 'package:lints/core.yaml'. These are just the most critical lints
# (the recommended set includes the core lints).
# The core lints are also what is used by pub.dev for scoring packages.

include: package:lints/recommended.yaml

# Uncomment the following section to specify additional rules.

# linter:
# rules:
# - camel_case_types

# analyzer:
# exclude:
# - path/to/excluded/files/**

# For more information about the core and recommended set of lints, see
# https://dart.dev/go/core-lints

# For additional information about configuring this file, see
# https://dart.dev/guides/language/analysis-options
include: package:very_good_analysis/analysis_options.yaml

analyzer:
errors:
unawaited_futures: warning
avoid_void_async: warning
missing_return: error
missing_required_param: error
invalid_annotation_target: info

language:
strict-casts: true
strict-inference: true
strict-raw-types: true
4 changes: 3 additions & 1 deletion example/oauth_chopper_example.dart
Original file line number Diff line number Diff line change
@@ -1,4 +1,6 @@
// ignore_for_file: unused_local_variable
// ignore because its a example.
// ignore_for_file: unused_local_variable, prefer_const_declarations,
// ignore_for_file: prefer_const_constructors

import 'package:chopper/chopper.dart';
import 'package:oauth_chopper/oauth_chopper.dart';
Expand Down
2 changes: 1 addition & 1 deletion lib/oauth_chopper.dart
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
/// OAuthChopper for configuring OAuth authentication with [Chopper].
/// OAuthChopper for configuring OAuth authentication with Chopper.
///
/// More dartdocs go here.
library oauth_chopper;
Expand Down
12 changes: 7 additions & 5 deletions lib/src/extensions/request.dart
Original file line number Diff line number Diff line change
@@ -1,9 +1,11 @@
import 'package:chopper/chopper.dart';

/// Helper extension to easily apply a authorization header to a request.
extension ChopperRequest on Request {
Request addAuthorizationHeader(String token) {
final newHeaders = Map<String, String>.from(headers);
newHeaders['Authorization'] = 'Bearer $token';
return copyWith(headers: newHeaders);
}
/// Adds a authorization header with a bearer [token] to the request.
Request addAuthorizationHeader(String token) => applyHeader(
this,
'Authorization',
'Bearer $token',
);
}
21 changes: 17 additions & 4 deletions lib/src/oauth_authenticator.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,20 +4,33 @@ import 'package:chopper/chopper.dart';
import 'package:oauth_chopper/oauth_chopper.dart';
import 'package:oauth_chopper/src/extensions/request.dart';

/// Callback for error handling.
typedef OnErrorCallback = void Function(Object, StackTrace);

/// OAuthAuthenticator provides a authenticator that handles OAuth authorizations.
/// {@template authenticator}
/// OAuthAuthenticator provides a authenticator that handles
/// OAuth authorizations.
/// When the provided credentials are invalid it tries to refresh them.
/// Can throw a exceptions if no [onError] is passed. When [onError] is passed exception will be passed to [onError]
/// Can throw a exceptions if no [onError] is passed. When [onError] is passed
/// exception will be passed to [onError]
/// {@endtemplate}
class OAuthAuthenticator extends Authenticator {
/// {@macro authenticator}
OAuthAuthenticator(this.oauthChopper, this.onError);

/// Callback for error handling.
final OnErrorCallback? onError;

/// The [OAuthChopper] instance to get the token from and
/// to refresh the token.
final OAuthChopper oauthChopper;

@override
FutureOr<Request?> authenticate(Request request, Response<dynamic> response,
[Request? originalRequest]) async {
FutureOr<Request?> authenticate(
Request request,
Response<dynamic> response, [
Request? originalRequest,
]) async {
final token = await oauthChopper.token;
if (response.statusCode == 401 && token != null) {
try {
Expand Down
48 changes: 29 additions & 19 deletions lib/src/oauth_chopper.dart
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,8 @@ import 'package:oauth_chopper/src/oauth_token.dart';
import 'package:oauth_chopper/src/storage/memory_storage.dart';
import 'package:oauth_chopper/src/storage/oauth_storage.dart';

/// OAuthChopper client for configuring OAuth authentication with [Chopper].
/// {@template oauth_chopper}
/// OAuthChopper client for configuring OAuth authentication with Chopper.
///
/// For example:
/// ```dart
Expand All @@ -18,7 +19,22 @@ import 'package:oauth_chopper/src/storage/oauth_storage.dart';
/// secret: secret,
/// );
/// ```
/// {@endtemplate}
class OAuthChopper {
/// {@macro oauth_chopper}
OAuthChopper({
required this.authorizationEndpoint,
required this.identifier,
required this.secret,
this.endSessionEndpoint,

/// OAuth storage for storing credentials.
/// By default it will use a in memory storage [MemoryStorage].
/// For persisting the credentials implement a custom [OAuthStorage].
/// See [OAuthStorage] for more information.
OAuthStorage? storage,
}) : _storage = storage ?? MemoryStorage();

/// OAuth authorization endpoint.
final Uri authorizationEndpoint;

Expand All @@ -32,22 +48,11 @@ class OAuthChopper {
final String secret;

/// OAuth storage for storing credentials.
/// By default it will use a in memory storage. For persisting the credentials implement a custom [OAuthStorage].
/// By default it will use a in memory storage. For persisting the credentials
/// implement a custom [OAuthStorage].
/// See [OAuthStorage] for more information.
final OAuthStorage _storage;

OAuthChopper({
required this.authorizationEndpoint,
required this.identifier,
required this.secret,
this.endSessionEndpoint,

/// OAuth storage for storing credentials.
/// By default it will use a in memory storage [MemoryStorage]. For persisting the credentials implement a custom [OAuthStorage].
/// See [OAuthStorage] for more information.
OAuthStorage? storage,
}) : _storage = storage ?? MemoryStorage();

/// Get stored [OAuthToken].
Future<OAuthToken?> get token async {
final credentialsJson = await _storage.fetchCredentials();
Expand All @@ -57,18 +62,21 @@ class OAuthChopper {
}

/// Provides an [OAuthAuthenticator] instance.
/// The authenticator can throw exceptions when OAuth authentication fails. If [onError] is provided exceptions will be passed to [onError] and not be thrown.
/// The authenticator can throw exceptions when OAuth authentication fails.
/// If [onError] is provided exceptions will be passed to [onError] and not be
/// thrown.
OAuthAuthenticator authenticator({
/// When provided [onError] handles exceptions if thrown.
OnErrorCallback? onError,
}) =>
OAuthAuthenticator(this, onError);

/// Provides an [OAuthInterceptor] instance.
OAuthInterceptor get interceptor => OAuthInterceptor(this);

/// Tries to refresh the available credentials and returns a new [OAuthToken] instance.
/// Throws an exception when refreshing fails. If the exception is a [AuthorizationException] it clears the storage.
/// Tries to refresh the available credentials and returns a new [OAuthToken]
/// instance.
/// Throws an exception when refreshing fails. If the exception is a
/// [AuthorizationException] it clears the storage.
/// See [Credentials.refresh]
Future<OAuthToken?> refresh() async {
final credentialsJson = await _storage.fetchCredentials();
Expand All @@ -85,7 +93,9 @@ class OAuthChopper {
}
}

/// Request an [OAuthGrant] and stores the credentials in the [storage].
/// Request an [OAuthGrant] and stores the credentials in the
/// [_storage].
///
/// Currently supported grants:
/// - [ResourceOwnerPasswordGrant]
/// - [ClientCredentialsGrant]
Expand Down
88 changes: 72 additions & 16 deletions lib/src/oauth_grant.dart
Original file line number Diff line number Diff line change
@@ -1,23 +1,46 @@
import 'package:oauth2/oauth2.dart' as oauth;

abstract class OAuthGrant {
/// {@template oauth_grant}
/// Interface for a OAuth grant.
/// Grants are used to obtain credentials from an authorization server.
/// {@endtemplate}
abstract interface class OAuthGrant {
/// {@macro oauth_grant}
const OAuthGrant();

/// Obtains credentials from an authorization server.
Future<String> handle(
Uri authorizationEndpoint, String identifier, String secret);
Uri authorizationEndpoint,
String identifier,
String secret,
);
}

/// {@template resource_owner_password_grant}
/// Obtains credentials using a [resource owner password grant](https://tools.ietf.org/html/rfc6749#section-1.3.3).
class ResourceOwnerPasswordGrant extends OAuthGrant {
///
/// This grant uses the resource owner's [username] and [password] to obtain
/// credentials.
/// {@endtemplate}
class ResourceOwnerPasswordGrant implements OAuthGrant {
/// {@macro resource_owner_password_grant}
const ResourceOwnerPasswordGrant({
required this.username,
required this.password,
});

/// Username used for obtaining credentials.
final String username;
final String password;

const ResourceOwnerPasswordGrant(
{required this.username, required this.password});
/// Password used for obtaining credentials.
final String password;

@override
Future<String> handle(
Uri authorizationEndpoint, String identifier, String secret) async {
Uri authorizationEndpoint,
String identifier,
String secret,
) async {
final client = await oauth.resourceOwnerPasswordGrant(
authorizationEndpoint,
username,
Expand All @@ -29,13 +52,19 @@ class ResourceOwnerPasswordGrant extends OAuthGrant {
}
}

/// {@template client_credentials_grant}
/// Obtains credentials using a [client credentials grant](https://tools.ietf.org/html/rfc6749#section-1.3.4).
class ClientCredentialsGrant extends OAuthGrant {
/// {@endtemplate}
class ClientCredentialsGrant implements OAuthGrant {
/// {@macro client_credentials_grant}
const ClientCredentialsGrant();

@override
Future<String> handle(
Uri authorizationEndpoint, String identifier, String secret) async {
Uri authorizationEndpoint,
String identifier,
String secret,
) async {
final client = await oauth.clientCredentialsGrant(
authorizationEndpoint,
identifier,
Expand All @@ -45,8 +74,11 @@ class ClientCredentialsGrant extends OAuthGrant {
}
}

/// {@template authorization_code_grant}
/// Obtains credentials using a [authorization code grant](https://tools.ietf.org/html/rfc6749#section-1.3.1).
class AuthorizationCodeGrant extends OAuthGrant {
/// {@endtemplate}
class AuthorizationCodeGrant implements OAuthGrant {
/// {@macro authorization_code_grant}
const AuthorizationCodeGrant({
required this.tokenEndpoint,
required this.scopes,
Expand All @@ -55,26 +87,50 @@ class AuthorizationCodeGrant extends OAuthGrant {
required this.listen,
});

/// A URL provided by the authorization server that this library uses to
/// obtain long-lasting credentials.
///
/// This will usually be listed in the authorization server's OAuth2 API
/// documentation.
final Uri tokenEndpoint;

/// 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 used for redirect the authorizationUrl given by the authorization
/// server.
final Future<void> Function(Uri authorizationUri) redirect;

/// Callback used for listening for the redirectUrl.
final Future<Uri> Function(Uri redirectUri) listen;

@override
Future<String> handle(
Uri authorizationEndpoint, String identifier, String secret) async {
Uri authorizationEndpoint,
String identifier,
String secret,
) async {
final grant = oauth.AuthorizationCodeGrant(
identifier,
authorizationEndpoint,
tokenEndpoint,
);
var authorizationUrl =
grant.getAuthorizationUrl(redirectUrl, scopes: scopes);

final authorizationUrl = grant.getAuthorizationUrl(
redirectUrl,
scopes: scopes,
);

await redirect(authorizationUrl);
var responseUrl = await listen(redirectUrl);
oauth.Client client =
await grant.handleAuthorizationResponse(responseUrl.queryParameters);
final responseUrl = await listen(redirectUrl);

final client = await grant.handleAuthorizationResponse(
responseUrl.queryParameters,
);

return client.credentials.toJson();
}
Expand Down
10 changes: 8 additions & 2 deletions lib/src/oauth_interceptor.dart
Original file line number Diff line number Diff line change
Expand Up @@ -4,12 +4,18 @@ import 'package:chopper/chopper.dart';
import 'package:oauth_chopper/oauth_chopper.dart';
import 'package:oauth_chopper/src/extensions/request.dart';

/// OAuthInterceptor is responsible for adding 'Authorization' header to requests.
/// The header is only added if there is a token available. When no token is available no header is added.
/// {@template oauth_interceptor}
/// OAuthInterceptor is responsible for adding 'Authorization' header to
/// requests.
/// The header is only added if there is a token available. When no token is
/// available no header is added.
/// Its added as a Bearer token.
/// {@endtemplate}
class OAuthInterceptor implements RequestInterceptor {
/// {@macro oauth_interceptor}
OAuthInterceptor(this.oauthChopper);

/// The [OAuthChopper] instance to get the token from.
final OAuthChopper oauthChopper;

@override
Expand Down
Loading

0 comments on commit 4162fe5

Please sign in to comment.