Skip to content

Commit 92776cc

Browse files
committed
feedback
1 parent 912af44 commit 92776cc

File tree

8 files changed

+88
-36
lines changed

8 files changed

+88
-36
lines changed

pkgs/oauth2/lib/src/authorization_code_grant.dart

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -149,7 +149,14 @@ class AuthorizationCodeGrant {
149149
/// as its body as a UTF-8-decoded string. It should return a map in the same
150150
/// format as the [standard JSON response][].
151151
///
152+
/// [customAuth] is an optional callback to add additional client
153+
/// authentication headers or body parameters to a token request for advanced
154+
/// scenarios, such as when using a JWT Bearer token for client authentication
155+
/// per [RFC 7523]. When provided, it replaces the default `basicAuth`
156+
/// credentials integration in token requests.
157+
///
152158
/// [standard JSON response]: https://tools.ietf.org/html/rfc6749#section-5.1
159+
/// [RFC 7523]: https://tools.ietf.org/html/rfc7523#section-2.2
153160
AuthorizationCodeGrant(
154161
this.identifier, this.authorizationEndpoint, this.tokenEndpoint,
155162
{this.secret,

pkgs/oauth2/lib/src/client.dart

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -88,7 +88,15 @@ class Client extends http.BaseClient {
8888
/// [httpClient] is the underlying client that this forwards requests to after
8989
/// adding authorization credentials to them.
9090
///
91+
/// [customAuth] is an optional callback to add additional client
92+
/// authentication headers or body parameters to a token request for advanced
93+
/// scenarios, such as when using a JWT Bearer token for client authentication
94+
/// per [RFC 7523]. When provided, it replaces the default `basicAuth`
95+
/// credentials integration during refresh token requests.
96+
///
9197
/// Throws an [ArgumentError] if [secret] is passed without [identifier].
98+
///
99+
/// [RFC 7523]: https://tools.ietf.org/html/rfc7523#section-2.2
92100
Client(this._credentials,
93101
{this.identifier,
94102
this.secret,

pkgs/oauth2/lib/src/client_credentials_grant.dart

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -39,7 +39,14 @@ import 'utils.dart';
3939
///
4040
/// This function is passed the `Content-Type` header of the response as well as
4141
/// its body as a UTF-8-decoded string. It should return a map in the same
42-
/// format as the [standard JSON response](https://tools.ietf.org/html/rfc6749#section-5.1)
42+
/// format as the [standard JSON response](https://tools.ietf.org/html/rfc6749#section-5.1).
43+
///
44+
/// [customAuth] is an optional callback to add additional client
45+
/// authentication headers or body parameters to a token request for advanced
46+
/// scenarios, such as when using a JWT Bearer token for client authentication
47+
/// per [RFC 7523](https://tools.ietf.org/html/rfc7523#section-2.2). When
48+
/// provided, it replaces the default `basicAuth` credentials integration in
49+
/// token requests.
4350
Future<Client> clientCredentialsGrant(
4451
Uri authorizationEndpoint, String? identifier, String? secret,
4552
{Iterable<String>? scopes,

pkgs/oauth2/lib/src/credentials.dart

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -208,6 +208,13 @@ class Credentials {
208208
/// You may request different scopes than the default by passing in
209209
/// [newScopes]. These must be a subset of [scopes].
210210
///
211+
/// [customAuth] is an optional callback to add additional client
212+
/// authentication headers or body parameters to a token request for advanced
213+
/// scenarios, such as when using a JWT Bearer token for client authentication
214+
/// per [RFC 7523](https://tools.ietf.org/html/rfc7523#section-2.2). When
215+
/// provided, it replaces the default `basicAuth` credentials integration in
216+
/// token requests.
217+
///
211218
/// This throws an [ArgumentError] if [secret] is passed without [identifier],
212219
/// a [StateError] if these credentials can't be refreshed, an
213220
/// [AuthorizationException] if refreshing the credentials fails, or a

pkgs/oauth2/lib/src/discovery.dart

Lines changed: 26 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -6,8 +6,18 @@ import 'dart:convert';
66

77
import 'package:http/http.dart' as http;
88

9+
/// An exception thrown when OAuth 2.0 discovery fails.
10+
class DiscoveryException implements Exception {
11+
final String message;
12+
13+
DiscoveryException(this.message);
14+
15+
@override
16+
String toString() => message;
17+
}
18+
919
/// OAuth 2.0 Authorization Server Metadata (RFC 8414).
10-
class OAuthServerMetadata {
20+
final class OAuthServerMetadata {
1121
/// The authorization server's issuer identifier.
1222
final String issuer;
1323

@@ -17,7 +27,8 @@ class OAuthServerMetadata {
1727
/// URL of the authorization server's token endpoint.
1828
final String tokenEndpoint;
1929

20-
/// URL of the authorization server's OAuth 2.0 Dynamic Client Registration endpoint.
30+
/// URL of the authorization server's OAuth 2.0 Dynamic Client Registration
31+
/// endpoint.
2132
final String? registrationEndpoint;
2233

2334
/// JSON array containing a list of the OAuth 2.0 scope values that this
@@ -40,8 +51,8 @@ class OAuthServerMetadata {
4051
/// by this authorization server.
4152
final List<String>? codeChallengeMethodsSupported;
4253

43-
/// Boolean value specifying whether the authorization server supports multiple
44-
/// issuers.
54+
/// Boolean value specifying whether the authorization server supports
55+
/// multiple issuers.
4556
final bool? clientIdMetadataDocumentSupported;
4657

4758
const OAuthServerMetadata({
@@ -80,11 +91,12 @@ class OAuthServerMetadata {
8091
}
8192

8293
/// OAuth 2.0 Protected Resource Metadata (RFC 9728).
83-
class OAuthProtectedResourceMetadata {
94+
final class OAuthProtectedResourceMetadata {
8495
/// A URI that identifies the protected resource.
8596
final String resource;
8697

87-
/// JSON array of authorization server identifiers that the protected resource trusts.
98+
/// JSON array of authorization server identifiers that the protected resource
99+
/// trusts.
88100
final List<String>? authorizationServers;
89101

90102
/// JSON array of the scope values that the protected resource supports.
@@ -134,7 +146,7 @@ Future<OAuthServerMetadata?> discoverAuthorizationServerMetadata(
134146
if (response.statusCode >= 400 && response.statusCode < 500) {
135147
continue;
136148
}
137-
throw StateError(
149+
throw DiscoveryException(
138150
'HTTP ${response.statusCode} loading authorization server '
139151
'metadata from $endpoint',
140152
);
@@ -146,14 +158,14 @@ Future<OAuthServerMetadata?> discoverAuthorizationServerMetadata(
146158
final expectedIssuer = authorizationServerUrl.toString();
147159
if (metadata.issuer.replaceAll(RegExp(r'/$'), '') !=
148160
expectedIssuer.replaceAll(RegExp(r'/$'), '')) {
149-
throw StateError(
161+
throw DiscoveryException(
150162
'Issuer spoofing detected: metadata issuer "${metadata.issuer}" '
151163
'does not match expected "$expectedIssuer".',
152164
);
153165
}
154166
return metadata;
155167
} catch (e) {
156-
if (e is StateError) rethrow;
168+
if (e is DiscoveryException) rethrow;
157169
continue;
158170
}
159171
}
@@ -166,7 +178,8 @@ Future<OAuthServerMetadata?> discoverAuthorizationServerMetadata(
166178
/// Discovers RFC 9728 OAuth 2.0 Protected Resource Metadata.
167179
///
168180
/// The returned [Future] completes with the metadata. It completes with a
169-
/// [StateError] if the metadata endpoint is not found (HTTP 404).
181+
/// [DiscoveryException] if the metadata endpoint is not found (HTTP 404) or
182+
/// returns another invalid response.
170183
Future<OAuthProtectedResourceMetadata> discoverProtectedResourceMetadata(
171184
Uri serverUrl, {
172185
Uri? resourceMetadataUrl,
@@ -189,13 +202,13 @@ Future<OAuthProtectedResourceMetadata> discoverProtectedResourceMetadata(
189202

190203
final response = await client.get(url);
191204
if (response.statusCode == 404) {
192-
throw StateError(
205+
throw DiscoveryException(
193206
'Resource server does not implement OAuth 2.0 Protected Resource '
194207
'Metadata.',
195208
);
196209
}
197210
if (response.statusCode < 200 || response.statusCode >= 300) {
198-
throw StateError(
211+
throw DiscoveryException(
199212
'HTTP ${response.statusCode} loading protected resource metadata.',
200213
);
201214
}
@@ -206,7 +219,7 @@ Future<OAuthProtectedResourceMetadata> discoverProtectedResourceMetadata(
206219
final expectedResource = serverUrl.toString();
207220
if (metadata.resource.replaceAll(RegExp(r'/$'), '') !=
208221
expectedResource.replaceAll(RegExp(r'/$'), '')) {
209-
throw StateError(
222+
throw DiscoveryException(
210223
'Resource spoofing detected: metadata resource "${metadata.resource}" '
211224
'does not match expected "$expectedResource".',
212225
);

pkgs/oauth2/lib/src/registration.dart

Lines changed: 21 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -16,14 +16,16 @@ final class OAuthClientMetadata {
1616
/// Array of redirection URI strings for use in redirect-based flows.
1717
final List<String> redirectUris;
1818

19-
/// String indicator of the requested authentication method for the token endpoint.
19+
/// String indicator of the requested authentication method for the token
20+
/// endpoint.
2021
final String? tokenEndpointAuthMethod;
2122

22-
/// Array of OAuth 2.0 grant type strings that the client can use at the token endpoint.
23+
/// Array of OAuth 2.0 grant type strings that the client can use at the
24+
/// token endpoint.
2325
final List<String>? grantTypes;
2426

25-
/// Array of the OAuth 2.0 response type strings that the client can use at the
26-
/// authorization endpoint.
27+
/// Array of the OAuth 2.0 response type strings that the client can use at
28+
/// the authorization endpoint.
2729
final List<String>? responseTypes;
2830

2931
/// Human-readable string name of the client to be presented to the end-user.
@@ -40,7 +42,8 @@ final class OAuthClientMetadata {
4042
/// software publisher used by registration endpoints.
4143
final String? softwareId;
4244

43-
/// A version identifier string for the client software identified by [softwareId].
45+
/// A version identifier string for the client software identified by
46+
/// [softwareId].
4447
final String? softwareVersion;
4548

4649
const OAuthClientMetadata({
@@ -74,8 +77,9 @@ final class OAuthClientMetadata {
7477
/// OAuth 2.0 Client Information ([RFC 7591]).
7578
///
7679
/// [RFC 7591]: https://datatracker.ietf.org/doc/html/rfc7591#section-3.2.1
77-
class OAuthClientInformation {
78-
/// Opaque value used by the client to identify itself to the authorization server.
80+
final class OAuthClientInformation {
81+
/// Opaque value used by the client to identify itself to the authorization
82+
/// server.
7983
final String clientId;
8084

8185
/// String value specifying the client secret.
@@ -91,8 +95,8 @@ class OAuthClientInformation {
9195
/// Given as seconds since epoch.
9296
final int? clientSecretExpiresAt;
9397

94-
/// String indicator of the authentication method that the authorization server
95-
/// will accept from the client when using the token endpoint.
98+
/// String indicator of the authentication method that the authorization
99+
/// server will accept from the client when using the token endpoint.
96100
final String? tokenEndpointAuthMethod;
97101

98102
const OAuthClientInformation({
@@ -116,7 +120,9 @@ class OAuthClientInformation {
116120

117121
/// Performs RFC 7591 Dynamic Client Registration.
118122
///
119-
/// Throws [AuthorizationException] if registration fails.
123+
/// Dynamic Client Registration allows OAuth 2.0 clients to register with an
124+
/// authorization server dynamically and obtain client credentials (such as
125+
/// a client ID and client secret) required to interact with the server.
120126
///
121127
/// The returned [Future] completes with the client information. It will
122128
/// complete with an [AuthorizationException] if the request is rejected by the
@@ -162,8 +168,11 @@ Future<OAuthClientInformation> registerClient(
162168
var uri = uriString == null ? null : Uri.parse(uriString);
163169
throw AuthorizationException(body['error'] as String, description, uri);
164170
}
165-
throw StateError(
166-
'HTTP ${response.statusCode} registering client at $registrationUrl');
171+
throw AuthorizationException(
172+
'server_error',
173+
'HTTP ${response.statusCode} registering client at $registrationUrl',
174+
null,
175+
);
167176
}
168177
return OAuthClientInformation.fromJson(
169178
jsonDecode(response.body) as Map<String, dynamic>,

pkgs/oauth2/test/discovery_test.dart

Lines changed: 8 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -77,7 +77,7 @@ void main() {
7777
expect(metadata, isNotNull);
7878
});
7979

80-
test('throws StateError on issuer mismatch', () async {
80+
test('throws DiscoveryException on issuer mismatch', () async {
8181
var client = ExpectClient()
8282
..expectRequest((request) {
8383
return Future.value(http.Response(
@@ -95,10 +95,10 @@ void main() {
9595
discoverAuthorizationServerMetadata(
9696
Uri.parse('https://server.example.com'),
9797
httpClient: client),
98-
throwsStateError);
98+
throwsA(isA<DiscoveryException>()));
9999
});
100100

101-
test('throws StateError on unexpected error', () async {
101+
test('throws DiscoveryException on unexpected error', () async {
102102
var client = ExpectClient()
103103
..expectRequest((request) {
104104
return Future.value(http.Response('', 500));
@@ -108,7 +108,7 @@ void main() {
108108
discoverAuthorizationServerMetadata(
109109
Uri.parse('https://server.example.com'),
110110
httpClient: client),
111-
throwsStateError);
111+
throwsA(isA<DiscoveryException>()));
112112
});
113113

114114
test('throws ArgumentError on insecure URL', () async {
@@ -172,7 +172,7 @@ void main() {
172172
expect(metadata.resource, equals('https://resource.example.com/v1/api'));
173173
});
174174

175-
test('throws StateError on resource mismatch', () async {
175+
test('throws DiscoveryException on resource mismatch', () async {
176176
var client = ExpectClient()
177177
..expectRequest((request) {
178178
return Future.value(http.Response(
@@ -188,10 +188,10 @@ void main() {
188188
discoverProtectedResourceMetadata(
189189
Uri.parse('https://resource.example.com'),
190190
httpClient: client),
191-
throwsStateError);
191+
throwsA(isA<DiscoveryException>()));
192192
});
193193

194-
test('throws StateError for 404', () async {
194+
test('throws DiscoveryException for 404', () async {
195195
var client = ExpectClient()
196196
..expectRequest((request) {
197197
return Future.value(http.Response('', 404));
@@ -201,7 +201,7 @@ void main() {
201201
discoverProtectedResourceMetadata(
202202
Uri.parse('https://resource.example.com'),
203203
httpClient: client),
204-
throwsStateError);
204+
throwsA(isA<DiscoveryException>()));
205205
});
206206

207207
test('throws ArgumentError on insecure URL', () async {

pkgs/oauth2/test/registration_test.dart

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -101,7 +101,8 @@ void main() {
101101
(e) => e.error, 'error', equals('invalid_redirect_uri'))));
102102
});
103103

104-
test('throws StateError on unexpected status code without JSON', () async {
104+
test('throws AuthorizationException on unexpected status code without JSON',
105+
() async {
105106
var client = ExpectClient()
106107
..expectRequest((request) {
107108
return Future.value(http.Response('Internal Server Error', 500));
@@ -113,7 +114,7 @@ void main() {
113114
expect(
114115
registerClient(Uri.parse('https://server.example.com'), metadata,
115116
httpClient: client),
116-
throwsStateError);
117+
throwsA(isA<AuthorizationException>()));
117118
});
118119

119120
test('throws ArgumentError on insecure authorizationServerUrl', () async {

0 commit comments

Comments
 (0)