platform/packages/auth/lib/src/configuration.dart

138 lines
4.3 KiB
Dart
Raw Normal View History

2019-01-04 15:47:01 +00:00
import 'package:charcode/ascii.dart';
2019-01-05 23:54:48 +00:00
import 'package:collection/collection.dart';
2021-03-20 23:51:20 +00:00
import 'package:quiver/core.dart';
2021-07-08 01:20:21 +00:00
import 'package:logging/logging.dart';
2019-01-04 15:47:01 +00:00
/// A common class containing parsing and validation logic for third-party authentication configuration.
class ExternalAuthOptions {
2021-07-08 01:20:21 +00:00
static final _log = Logger('VirtualDirectory');
2019-01-04 15:47:01 +00:00
/// The user's identifier, otherwise known as an "application id".
2021-05-09 11:16:15 +00:00
final String clientId;
2019-01-04 15:47:01 +00:00
/// The user's secret, other known as an "application secret".
2021-05-09 11:16:15 +00:00
final String clientSecret;
2019-01-04 15:47:01 +00:00
/// The user's redirect URI.
final Uri redirectUri;
2019-01-05 23:54:48 +00:00
/// The scopes to be passed to the external server.
final Set<String> scopes;
ExternalAuthOptions._(
2021-05-09 11:16:15 +00:00
this.clientId, this.clientSecret, this.redirectUri, this.scopes);
2019-01-04 15:47:01 +00:00
factory ExternalAuthOptions(
2021-05-09 11:16:15 +00:00
{required String clientId,
required String clientSecret,
2021-03-20 23:51:20 +00:00
required redirectUri,
2019-01-05 23:54:48 +00:00
Iterable<String> scopes = const []}) {
2019-01-04 15:47:01 +00:00
if (redirectUri is String) {
2019-04-19 07:50:04 +00:00
return ExternalAuthOptions._(
2019-01-05 23:54:48 +00:00
clientId, clientSecret, Uri.parse(redirectUri), scopes.toSet());
2019-01-04 15:47:01 +00:00
} else if (redirectUri is Uri) {
2019-04-19 07:50:04 +00:00
return ExternalAuthOptions._(
2019-01-05 23:54:48 +00:00
clientId, clientSecret, redirectUri, scopes.toSet());
2019-01-04 15:47:01 +00:00
} else {
2021-07-08 01:20:21 +00:00
_log.severe('RedirectUri is not valid');
2019-04-19 07:50:04 +00:00
throw ArgumentError.value(
2019-01-04 15:47:01 +00:00
redirectUri, 'redirectUri', 'must be a String or Uri');
}
}
/// Returns a JSON-friendly representation of this object.
///
/// Parses the following fields:
/// * `client_id`
/// * `client_secret`
/// * `redirect_uri`
factory ExternalAuthOptions.fromMap(Map map) {
2021-05-11 13:16:37 +00:00
var clientId = map['client_id'];
var clientSecret = map['client_secret'];
if (clientId == null || clientSecret == null) {
2021-07-08 01:20:21 +00:00
_log.severe('clientId or clientSecret is null');
2021-05-11 13:16:37 +00:00
throw ArgumentError('Invalid clientId and/or clientSecret');
}
2019-04-19 07:50:04 +00:00
return ExternalAuthOptions(
2021-05-11 13:16:37 +00:00
clientId: clientId as String,
clientSecret: clientSecret as String,
2019-01-04 15:47:01 +00:00
redirectUri: map['redirect_uri'],
2019-01-05 23:54:48 +00:00
scopes: map['scopes'] is Iterable
? ((map['scopes'] as Iterable).map((x) => x.toString()))
: <String>[],
2019-01-04 15:47:01 +00:00
);
}
@override
2019-01-05 23:54:48 +00:00
int get hashCode => hash4(clientId, clientSecret, redirectUri, scopes);
2019-01-04 15:47:01 +00:00
@override
bool operator ==(other) =>
other is ExternalAuthOptions &&
other.clientId == clientId &&
other.clientSecret == other.clientSecret &&
2019-01-05 23:54:48 +00:00
other.redirectUri == other.redirectUri &&
const SetEquality<String>().equals(other.scopes, scopes);
2019-01-04 15:47:01 +00:00
/// Creates a copy of this object, with the specified changes.
ExternalAuthOptions copyWith(
2021-05-11 13:16:37 +00:00
{String? clientId,
String? clientSecret,
2019-01-05 23:54:48 +00:00
redirectUri,
2021-05-09 11:16:15 +00:00
Iterable<String> scopes = const []}) {
2019-04-19 07:50:04 +00:00
return ExternalAuthOptions(
2021-05-11 13:16:37 +00:00
clientId: clientId ?? this.clientId,
clientSecret: clientSecret ?? this.clientSecret,
2019-01-04 15:47:01 +00:00
redirectUri: redirectUri ?? this.redirectUri,
2021-05-09 11:16:15 +00:00
scopes: (scopes).followedBy(this.scopes),
2019-01-04 15:47:01 +00:00
);
}
/// Returns a JSON-friendly representation of this object.
///
/// Contains the following fields:
/// * `client_id`
/// * `client_secret`
/// * `redirect_uri`
///
/// If [obscureSecret] is `true` (default), then the [clientSecret] will
/// be replaced by the string `<redacted>`.
2019-01-05 23:54:48 +00:00
Map<String, dynamic> toJson({bool obscureSecret = true}) {
2019-01-04 15:47:01 +00:00
return {
'client_id': clientId,
'client_secret': obscureSecret ? '<redacted>' : clientSecret,
'redirect_uri': redirectUri.toString(),
2019-01-05 23:54:48 +00:00
'scopes': scopes.toList(),
2019-01-04 15:47:01 +00:00
};
}
/// Returns a [String] representation of this object.
///
/// If [obscureText] is `true` (default), then the [clientSecret] will be
/// replaced by asterisks in the output.
///
/// If no [asteriskCount] is given, then the number of asterisks will equal the length of
/// the actual [clientSecret].
@override
2021-03-20 23:51:20 +00:00
String toString({bool obscureSecret = true, int? asteriskCount}) {
String? secret;
2019-01-04 15:47:01 +00:00
if (!obscureSecret) {
secret = clientSecret;
} else {
var codeUnits =
2021-05-09 11:16:15 +00:00
List<int>.filled(asteriskCount ?? clientSecret.length, $asterisk);
2019-04-19 07:50:04 +00:00
secret = String.fromCharCodes(codeUnits);
2019-01-04 15:47:01 +00:00
}
2019-04-19 07:50:04 +00:00
var b = StringBuffer('ExternalAuthOptions(');
2019-01-04 15:47:01 +00:00
b.write('clientId=$clientId');
b.write(', clientSecret=$secret');
b.write(', redirectUri=$redirectUri');
2019-01-05 23:54:48 +00:00
b.write(', scopes=${scopes.toList()}');
2019-01-04 15:47:01 +00:00
b.write(')');
return b.toString();
}
}