mirror of
https://github.com/foss42/apidash.git
synced 2025-12-02 10:49:49 +08:00
feat: add digest authentication
This commit is contained in:
139
lib/screens/common_widgets/auth/digest_auth_fields.dart
Normal file
139
lib/screens/common_widgets/auth/digest_auth_fields.dart
Normal file
@@ -0,0 +1,139 @@
|
||||
import 'package:apidash/screens/common_widgets/auth_textfield.dart';
|
||||
import 'package:apidash_core/apidash_core.dart';
|
||||
import 'package:apidash_design_system/widgets/widgets.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
|
||||
class DigestAuthFields extends StatefulWidget {
|
||||
final AuthModel? authData;
|
||||
final bool readOnly;
|
||||
final Function(AuthModel?) updateAuth;
|
||||
|
||||
const DigestAuthFields({
|
||||
super.key,
|
||||
required this.authData,
|
||||
required this.updateAuth,
|
||||
this.readOnly = false,
|
||||
});
|
||||
|
||||
@override
|
||||
State<DigestAuthFields> createState() => _DigestAuthFieldsState();
|
||||
}
|
||||
|
||||
class _DigestAuthFieldsState extends State<DigestAuthFields> {
|
||||
late TextEditingController _usernameController;
|
||||
late TextEditingController _passwordController;
|
||||
late TextEditingController _realmController;
|
||||
late TextEditingController _nonceController;
|
||||
late String _algorithmController;
|
||||
late TextEditingController _qopController;
|
||||
late TextEditingController _opaqueController;
|
||||
|
||||
@override
|
||||
void initState() {
|
||||
super.initState();
|
||||
final digest = widget.authData?.digest;
|
||||
_usernameController = TextEditingController(text: digest?.username ?? '');
|
||||
_passwordController = TextEditingController(text: digest?.password ?? '');
|
||||
_realmController = TextEditingController(text: digest?.realm ?? '');
|
||||
_nonceController = TextEditingController(text: digest?.nonce ?? '');
|
||||
_algorithmController = digest?.algorithm ?? 'MD5';
|
||||
_qopController = TextEditingController(text: digest?.qop ?? 'auth');
|
||||
_opaqueController = TextEditingController(text: digest?.opaque ?? '');
|
||||
}
|
||||
|
||||
@override
|
||||
Widget build(BuildContext context) {
|
||||
return Column(
|
||||
crossAxisAlignment: CrossAxisAlignment.start,
|
||||
children: [
|
||||
AuthTextField(
|
||||
readOnly: widget.readOnly,
|
||||
controller: _usernameController,
|
||||
hintText: "Username",
|
||||
onChanged: (_) => _updateDigestAuth(),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
AuthTextField(
|
||||
readOnly: widget.readOnly,
|
||||
controller: _passwordController,
|
||||
hintText: "Password",
|
||||
isObscureText: true,
|
||||
onChanged: (_) => _updateDigestAuth(),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
AuthTextField(
|
||||
readOnly: widget.readOnly,
|
||||
controller: _realmController,
|
||||
hintText: "Realm",
|
||||
onChanged: (_) => _updateDigestAuth(),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
AuthTextField(
|
||||
readOnly: widget.readOnly,
|
||||
controller: _nonceController,
|
||||
hintText: "Nonce",
|
||||
onChanged: (_) => _updateDigestAuth(),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
Text(
|
||||
"Algorithm",
|
||||
style: TextStyle(
|
||||
fontWeight: FontWeight.normal,
|
||||
fontSize: 14,
|
||||
),
|
||||
),
|
||||
SizedBox(height: 4),
|
||||
ADPopupMenu<String>(
|
||||
value: _algorithmController.trim(),
|
||||
values: const [
|
||||
('MD5', 'MD5'),
|
||||
('MD5-sess', 'MD5-sess'),
|
||||
('SHA-256', 'SHA-256'),
|
||||
('SHA-256-sess', 'SHA-256-sess'),
|
||||
],
|
||||
tooltip: "this algorithm will be used to produce the digest",
|
||||
isOutlined: true,
|
||||
onChanged: (String? newLocation) {
|
||||
if (newLocation != null) {
|
||||
setState(() {
|
||||
_algorithmController = newLocation;
|
||||
});
|
||||
_updateDigestAuth();
|
||||
}
|
||||
},
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
AuthTextField(
|
||||
readOnly: widget.readOnly,
|
||||
controller: _qopController,
|
||||
hintText: "QOP (e.g. auth)",
|
||||
onChanged: (_) => _updateDigestAuth(),
|
||||
),
|
||||
const SizedBox(height: 16),
|
||||
AuthTextField(
|
||||
readOnly: widget.readOnly,
|
||||
controller: _opaqueController,
|
||||
hintText: "Opaque",
|
||||
onChanged: (_) => _updateDigestAuth(),
|
||||
),
|
||||
],
|
||||
);
|
||||
}
|
||||
|
||||
void _updateDigestAuth() {
|
||||
widget.updateAuth(
|
||||
widget.authData?.copyWith(
|
||||
type: APIAuthType.digest,
|
||||
digest: AuthDigestModel(
|
||||
username: _usernameController.text.trim(),
|
||||
password: _passwordController.text.trim(),
|
||||
realm: _realmController.text.trim(),
|
||||
nonce: _nonceController.text.trim(),
|
||||
algorithm: _algorithmController.trim(),
|
||||
qop: _qopController.text.trim(),
|
||||
opaque: _opaqueController.text.trim(),
|
||||
),
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
@@ -1,6 +1,7 @@
|
||||
import 'package:apidash/screens/common_widgets/auth/api_key_auth_fields.dart';
|
||||
import 'package:apidash/screens/common_widgets/auth/basic_auth_fields.dart';
|
||||
import 'package:apidash/screens/common_widgets/auth/bearer_auth_fields.dart';
|
||||
import 'package:apidash/screens/common_widgets/auth/digest_auth_fields.dart';
|
||||
import 'package:apidash/screens/common_widgets/auth/jwt_auth_fields.dart';
|
||||
import 'package:apidash_design_system/widgets/popup_menu.dart';
|
||||
import 'package:flutter/material.dart';
|
||||
@@ -53,27 +54,6 @@ class EditAuthType extends ConsumerWidget {
|
||||
SizedBox(
|
||||
height: 8,
|
||||
),
|
||||
if (readOnly)
|
||||
Container(
|
||||
padding:
|
||||
const EdgeInsets.symmetric(horizontal: 12, vertical: 8),
|
||||
decoration: BoxDecoration(
|
||||
color: Theme.of(context).colorScheme.surfaceContainerLowest,
|
||||
borderRadius: BorderRadius.circular(8),
|
||||
border: Border.all(
|
||||
color:
|
||||
Theme.of(context).colorScheme.outline.withOpacity(0.3),
|
||||
),
|
||||
),
|
||||
child: Text(
|
||||
currentAuthType.name.toUpperCase(),
|
||||
style: Theme.of(context).textTheme.bodyMedium?.copyWith(
|
||||
fontWeight: FontWeight.w500,
|
||||
color: Theme.of(context).colorScheme.primary,
|
||||
),
|
||||
),
|
||||
)
|
||||
else
|
||||
ADPopupMenu<APIAuthType>(
|
||||
value: currentAuthType.name.capitalize(),
|
||||
values: const [
|
||||
@@ -88,11 +68,15 @@ class EditAuthType extends ConsumerWidget {
|
||||
],
|
||||
tooltip: "Select Authentication Type",
|
||||
isOutlined: true,
|
||||
onChanged: (APIAuthType? newType) {
|
||||
onChanged: readOnly
|
||||
? null
|
||||
: (APIAuthType? newType) {
|
||||
final selectedRequest =
|
||||
ref.read(selectedRequestModelProvider);
|
||||
if (newType != null) {
|
||||
ref.read(collectionStateNotifierProvider.notifier).update(
|
||||
ref
|
||||
.read(collectionStateNotifierProvider.notifier)
|
||||
.update(
|
||||
authModel: selectedRequest
|
||||
?.httpRequestModel?.authModel
|
||||
?.copyWith(type: newType) ??
|
||||
@@ -151,6 +135,12 @@ class EditAuthType extends ConsumerWidget {
|
||||
authData: authData,
|
||||
updateAuth: updateAuth,
|
||||
);
|
||||
case APIAuthType.digest:
|
||||
return DigestAuthFields(
|
||||
readOnly: readOnly,
|
||||
authData: authData,
|
||||
updateAuth: updateAuth,
|
||||
);
|
||||
case APIAuthType.none:
|
||||
return Text(readOnly
|
||||
? "No authentication was used for this request."
|
||||
|
||||
@@ -48,6 +48,14 @@ packages:
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "1.19.1"
|
||||
convert:
|
||||
dependency: transitive
|
||||
description:
|
||||
name: convert
|
||||
sha256: b30acd5944035672bc15c6b7a8b47d773e41e2f17de064350988c5d02adb1c68
|
||||
url: "https://pub.dev"
|
||||
source: hosted
|
||||
version: "3.1.2"
|
||||
crypto:
|
||||
dependency: transitive
|
||||
description:
|
||||
|
||||
@@ -98,4 +98,5 @@ const LineSplitter kSplitter = LineSplitter();
|
||||
const kCodeCharsPerLineLimit = 200;
|
||||
|
||||
const kHeaderContentType = "Content-Type";
|
||||
const kHeaderWwwAuthenticate = 'www-authenticate';
|
||||
const kMsgRequestCancelled = 'Request Cancelled';
|
||||
|
||||
@@ -4,22 +4,21 @@ import 'auth_api_key_model.dart';
|
||||
import 'auth_basic_model.dart';
|
||||
import 'auth_bearer_model.dart';
|
||||
import 'auth_jwt_model.dart';
|
||||
import 'auth_digest_model.dart';
|
||||
|
||||
part 'api_auth_model.g.dart';
|
||||
part 'api_auth_model.freezed.dart';
|
||||
|
||||
@freezed
|
||||
class AuthModel with _$AuthModel {
|
||||
@JsonSerializable(
|
||||
explicitToJson: true,
|
||||
anyMap: true,
|
||||
)
|
||||
@JsonSerializable(explicitToJson: true, anyMap: true)
|
||||
const factory AuthModel({
|
||||
required APIAuthType type,
|
||||
AuthApiKeyModel? apikey,
|
||||
AuthBearerModel? bearer,
|
||||
AuthBasicAuthModel? basic,
|
||||
AuthJwtModel? jwt,
|
||||
AuthDigestModel? digest,
|
||||
}) = _AuthModel;
|
||||
|
||||
factory AuthModel.fromJson(Map<String, dynamic> json) =>
|
||||
|
||||
@@ -26,6 +26,7 @@ mixin _$AuthModel {
|
||||
AuthBearerModel? get bearer => throw _privateConstructorUsedError;
|
||||
AuthBasicAuthModel? get basic => throw _privateConstructorUsedError;
|
||||
AuthJwtModel? get jwt => throw _privateConstructorUsedError;
|
||||
AuthDigestModel? get digest => throw _privateConstructorUsedError;
|
||||
|
||||
/// Serializes this AuthModel to a JSON map.
|
||||
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
|
||||
@@ -48,12 +49,14 @@ abstract class $AuthModelCopyWith<$Res> {
|
||||
AuthBearerModel? bearer,
|
||||
AuthBasicAuthModel? basic,
|
||||
AuthJwtModel? jwt,
|
||||
AuthDigestModel? digest,
|
||||
});
|
||||
|
||||
$AuthApiKeyModelCopyWith<$Res>? get apikey;
|
||||
$AuthBearerModelCopyWith<$Res>? get bearer;
|
||||
$AuthBasicAuthModelCopyWith<$Res>? get basic;
|
||||
$AuthJwtModelCopyWith<$Res>? get jwt;
|
||||
$AuthDigestModelCopyWith<$Res>? get digest;
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
@@ -76,6 +79,7 @@ class _$AuthModelCopyWithImpl<$Res, $Val extends AuthModel>
|
||||
Object? bearer = freezed,
|
||||
Object? basic = freezed,
|
||||
Object? jwt = freezed,
|
||||
Object? digest = freezed,
|
||||
}) {
|
||||
return _then(
|
||||
_value.copyWith(
|
||||
@@ -99,6 +103,10 @@ class _$AuthModelCopyWithImpl<$Res, $Val extends AuthModel>
|
||||
? _value.jwt
|
||||
: jwt // ignore: cast_nullable_to_non_nullable
|
||||
as AuthJwtModel?,
|
||||
digest: freezed == digest
|
||||
? _value.digest
|
||||
: digest // ignore: cast_nullable_to_non_nullable
|
||||
as AuthDigestModel?,
|
||||
)
|
||||
as $Val,
|
||||
);
|
||||
@@ -159,6 +167,20 @@ class _$AuthModelCopyWithImpl<$Res, $Val extends AuthModel>
|
||||
return _then(_value.copyWith(jwt: value) as $Val);
|
||||
});
|
||||
}
|
||||
|
||||
/// Create a copy of AuthModel
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@override
|
||||
@pragma('vm:prefer-inline')
|
||||
$AuthDigestModelCopyWith<$Res>? get digest {
|
||||
if (_value.digest == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return $AuthDigestModelCopyWith<$Res>(_value.digest!, (value) {
|
||||
return _then(_value.copyWith(digest: value) as $Val);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
@@ -176,6 +198,7 @@ abstract class _$$AuthModelImplCopyWith<$Res>
|
||||
AuthBearerModel? bearer,
|
||||
AuthBasicAuthModel? basic,
|
||||
AuthJwtModel? jwt,
|
||||
AuthDigestModel? digest,
|
||||
});
|
||||
|
||||
@override
|
||||
@@ -186,6 +209,8 @@ abstract class _$$AuthModelImplCopyWith<$Res>
|
||||
$AuthBasicAuthModelCopyWith<$Res>? get basic;
|
||||
@override
|
||||
$AuthJwtModelCopyWith<$Res>? get jwt;
|
||||
@override
|
||||
$AuthDigestModelCopyWith<$Res>? get digest;
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
@@ -207,6 +232,7 @@ class __$$AuthModelImplCopyWithImpl<$Res>
|
||||
Object? bearer = freezed,
|
||||
Object? basic = freezed,
|
||||
Object? jwt = freezed,
|
||||
Object? digest = freezed,
|
||||
}) {
|
||||
return _then(
|
||||
_$AuthModelImpl(
|
||||
@@ -230,6 +256,10 @@ class __$$AuthModelImplCopyWithImpl<$Res>
|
||||
? _value.jwt
|
||||
: jwt // ignore: cast_nullable_to_non_nullable
|
||||
as AuthJwtModel?,
|
||||
digest: freezed == digest
|
||||
? _value.digest
|
||||
: digest // ignore: cast_nullable_to_non_nullable
|
||||
as AuthDigestModel?,
|
||||
),
|
||||
);
|
||||
}
|
||||
@@ -245,6 +275,7 @@ class _$AuthModelImpl implements _AuthModel {
|
||||
this.bearer,
|
||||
this.basic,
|
||||
this.jwt,
|
||||
this.digest,
|
||||
});
|
||||
|
||||
factory _$AuthModelImpl.fromJson(Map<String, dynamic> json) =>
|
||||
@@ -260,10 +291,12 @@ class _$AuthModelImpl implements _AuthModel {
|
||||
final AuthBasicAuthModel? basic;
|
||||
@override
|
||||
final AuthJwtModel? jwt;
|
||||
@override
|
||||
final AuthDigestModel? digest;
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'AuthModel(type: $type, apikey: $apikey, bearer: $bearer, basic: $basic, jwt: $jwt)';
|
||||
return 'AuthModel(type: $type, apikey: $apikey, bearer: $bearer, basic: $basic, jwt: $jwt, digest: $digest)';
|
||||
}
|
||||
|
||||
@override
|
||||
@@ -275,13 +308,14 @@ class _$AuthModelImpl implements _AuthModel {
|
||||
(identical(other.apikey, apikey) || other.apikey == apikey) &&
|
||||
(identical(other.bearer, bearer) || other.bearer == bearer) &&
|
||||
(identical(other.basic, basic) || other.basic == basic) &&
|
||||
(identical(other.jwt, jwt) || other.jwt == jwt));
|
||||
(identical(other.jwt, jwt) || other.jwt == jwt) &&
|
||||
(identical(other.digest, digest) || other.digest == digest));
|
||||
}
|
||||
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@override
|
||||
int get hashCode =>
|
||||
Object.hash(runtimeType, type, apikey, bearer, basic, jwt);
|
||||
Object.hash(runtimeType, type, apikey, bearer, basic, jwt, digest);
|
||||
|
||||
/// Create a copy of AuthModel
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@@ -304,6 +338,7 @@ abstract class _AuthModel implements AuthModel {
|
||||
final AuthBearerModel? bearer,
|
||||
final AuthBasicAuthModel? basic,
|
||||
final AuthJwtModel? jwt,
|
||||
final AuthDigestModel? digest,
|
||||
}) = _$AuthModelImpl;
|
||||
|
||||
factory _AuthModel.fromJson(Map<String, dynamic> json) =
|
||||
@@ -319,6 +354,8 @@ abstract class _AuthModel implements AuthModel {
|
||||
AuthBasicAuthModel? get basic;
|
||||
@override
|
||||
AuthJwtModel? get jwt;
|
||||
@override
|
||||
AuthDigestModel? get digest;
|
||||
|
||||
/// Create a copy of AuthModel
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
|
||||
@@ -26,6 +26,11 @@ _$AuthModelImpl _$$AuthModelImplFromJson(Map json) => _$AuthModelImpl(
|
||||
jwt: json['jwt'] == null
|
||||
? null
|
||||
: AuthJwtModel.fromJson(Map<String, dynamic>.from(json['jwt'] as Map)),
|
||||
digest: json['digest'] == null
|
||||
? null
|
||||
: AuthDigestModel.fromJson(
|
||||
Map<String, dynamic>.from(json['digest'] as Map),
|
||||
),
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$$AuthModelImplToJson(_$AuthModelImpl instance) =>
|
||||
@@ -35,6 +40,7 @@ Map<String, dynamic> _$$AuthModelImplToJson(_$AuthModelImpl instance) =>
|
||||
'bearer': instance.bearer?.toJson(),
|
||||
'basic': instance.basic?.toJson(),
|
||||
'jwt': instance.jwt?.toJson(),
|
||||
'digest': instance.digest?.toJson(),
|
||||
};
|
||||
|
||||
const _$APIAuthTypeEnumMap = {
|
||||
|
||||
@@ -0,0 +1,20 @@
|
||||
import 'package:freezed_annotation/freezed_annotation.dart';
|
||||
|
||||
part 'auth_digest_model.g.dart';
|
||||
part 'auth_digest_model.freezed.dart';
|
||||
|
||||
@freezed
|
||||
class AuthDigestModel with _$AuthDigestModel {
|
||||
const factory AuthDigestModel({
|
||||
required String username,
|
||||
required String password,
|
||||
required String realm,
|
||||
required String nonce,
|
||||
required String algorithm,
|
||||
required String qop,
|
||||
required String opaque,
|
||||
}) = _AuthDigestModel;
|
||||
|
||||
factory AuthDigestModel.fromJson(Map<String, dynamic> json) =>
|
||||
_$AuthDigestModelFromJson(json);
|
||||
}
|
||||
@@ -0,0 +1,314 @@
|
||||
// coverage:ignore-file
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
// ignore_for_file: type=lint
|
||||
// ignore_for_file: unused_element, deprecated_member_use, deprecated_member_use_from_same_package, use_function_type_syntax_for_parameters, unnecessary_const, avoid_init_to_null, invalid_override_different_default_values_named, prefer_expression_function_bodies, annotate_overrides, invalid_annotation_target, unnecessary_question_mark
|
||||
|
||||
part of 'auth_digest_model.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// FreezedGenerator
|
||||
// **************************************************************************
|
||||
|
||||
T _$identity<T>(T value) => value;
|
||||
|
||||
final _privateConstructorUsedError = UnsupportedError(
|
||||
'It seems like you constructed your class using `MyClass._()`. This constructor is only meant to be used by freezed and you are not supposed to need it nor use it.\nPlease check the documentation here for more information: https://github.com/rrousselGit/freezed#adding-getters-and-methods-to-our-models',
|
||||
);
|
||||
|
||||
AuthDigestModel _$AuthDigestModelFromJson(Map<String, dynamic> json) {
|
||||
return _AuthDigestModel.fromJson(json);
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
mixin _$AuthDigestModel {
|
||||
String get username => throw _privateConstructorUsedError;
|
||||
String get password => throw _privateConstructorUsedError;
|
||||
String get realm => throw _privateConstructorUsedError;
|
||||
String get nonce => throw _privateConstructorUsedError;
|
||||
String get algorithm => throw _privateConstructorUsedError;
|
||||
String get qop => throw _privateConstructorUsedError;
|
||||
String get opaque => throw _privateConstructorUsedError;
|
||||
|
||||
/// Serializes this AuthDigestModel to a JSON map.
|
||||
Map<String, dynamic> toJson() => throw _privateConstructorUsedError;
|
||||
|
||||
/// Create a copy of AuthDigestModel
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
$AuthDigestModelCopyWith<AuthDigestModel> get copyWith =>
|
||||
throw _privateConstructorUsedError;
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract class $AuthDigestModelCopyWith<$Res> {
|
||||
factory $AuthDigestModelCopyWith(
|
||||
AuthDigestModel value,
|
||||
$Res Function(AuthDigestModel) then,
|
||||
) = _$AuthDigestModelCopyWithImpl<$Res, AuthDigestModel>;
|
||||
@useResult
|
||||
$Res call({
|
||||
String username,
|
||||
String password,
|
||||
String realm,
|
||||
String nonce,
|
||||
String algorithm,
|
||||
String qop,
|
||||
String opaque,
|
||||
});
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
class _$AuthDigestModelCopyWithImpl<$Res, $Val extends AuthDigestModel>
|
||||
implements $AuthDigestModelCopyWith<$Res> {
|
||||
_$AuthDigestModelCopyWithImpl(this._value, this._then);
|
||||
|
||||
// ignore: unused_field
|
||||
final $Val _value;
|
||||
// ignore: unused_field
|
||||
final $Res Function($Val) _then;
|
||||
|
||||
/// Create a copy of AuthDigestModel
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@pragma('vm:prefer-inline')
|
||||
@override
|
||||
$Res call({
|
||||
Object? username = null,
|
||||
Object? password = null,
|
||||
Object? realm = null,
|
||||
Object? nonce = null,
|
||||
Object? algorithm = null,
|
||||
Object? qop = null,
|
||||
Object? opaque = null,
|
||||
}) {
|
||||
return _then(
|
||||
_value.copyWith(
|
||||
username: null == username
|
||||
? _value.username
|
||||
: username // ignore: cast_nullable_to_non_nullable
|
||||
as String,
|
||||
password: null == password
|
||||
? _value.password
|
||||
: password // ignore: cast_nullable_to_non_nullable
|
||||
as String,
|
||||
realm: null == realm
|
||||
? _value.realm
|
||||
: realm // ignore: cast_nullable_to_non_nullable
|
||||
as String,
|
||||
nonce: null == nonce
|
||||
? _value.nonce
|
||||
: nonce // ignore: cast_nullable_to_non_nullable
|
||||
as String,
|
||||
algorithm: null == algorithm
|
||||
? _value.algorithm
|
||||
: algorithm // ignore: cast_nullable_to_non_nullable
|
||||
as String,
|
||||
qop: null == qop
|
||||
? _value.qop
|
||||
: qop // ignore: cast_nullable_to_non_nullable
|
||||
as String,
|
||||
opaque: null == opaque
|
||||
? _value.opaque
|
||||
: opaque // ignore: cast_nullable_to_non_nullable
|
||||
as String,
|
||||
)
|
||||
as $Val,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
abstract class _$$AuthDigestModelImplCopyWith<$Res>
|
||||
implements $AuthDigestModelCopyWith<$Res> {
|
||||
factory _$$AuthDigestModelImplCopyWith(
|
||||
_$AuthDigestModelImpl value,
|
||||
$Res Function(_$AuthDigestModelImpl) then,
|
||||
) = __$$AuthDigestModelImplCopyWithImpl<$Res>;
|
||||
@override
|
||||
@useResult
|
||||
$Res call({
|
||||
String username,
|
||||
String password,
|
||||
String realm,
|
||||
String nonce,
|
||||
String algorithm,
|
||||
String qop,
|
||||
String opaque,
|
||||
});
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
class __$$AuthDigestModelImplCopyWithImpl<$Res>
|
||||
extends _$AuthDigestModelCopyWithImpl<$Res, _$AuthDigestModelImpl>
|
||||
implements _$$AuthDigestModelImplCopyWith<$Res> {
|
||||
__$$AuthDigestModelImplCopyWithImpl(
|
||||
_$AuthDigestModelImpl _value,
|
||||
$Res Function(_$AuthDigestModelImpl) _then,
|
||||
) : super(_value, _then);
|
||||
|
||||
/// Create a copy of AuthDigestModel
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@pragma('vm:prefer-inline')
|
||||
@override
|
||||
$Res call({
|
||||
Object? username = null,
|
||||
Object? password = null,
|
||||
Object? realm = null,
|
||||
Object? nonce = null,
|
||||
Object? algorithm = null,
|
||||
Object? qop = null,
|
||||
Object? opaque = null,
|
||||
}) {
|
||||
return _then(
|
||||
_$AuthDigestModelImpl(
|
||||
username: null == username
|
||||
? _value.username
|
||||
: username // ignore: cast_nullable_to_non_nullable
|
||||
as String,
|
||||
password: null == password
|
||||
? _value.password
|
||||
: password // ignore: cast_nullable_to_non_nullable
|
||||
as String,
|
||||
realm: null == realm
|
||||
? _value.realm
|
||||
: realm // ignore: cast_nullable_to_non_nullable
|
||||
as String,
|
||||
nonce: null == nonce
|
||||
? _value.nonce
|
||||
: nonce // ignore: cast_nullable_to_non_nullable
|
||||
as String,
|
||||
algorithm: null == algorithm
|
||||
? _value.algorithm
|
||||
: algorithm // ignore: cast_nullable_to_non_nullable
|
||||
as String,
|
||||
qop: null == qop
|
||||
? _value.qop
|
||||
: qop // ignore: cast_nullable_to_non_nullable
|
||||
as String,
|
||||
opaque: null == opaque
|
||||
? _value.opaque
|
||||
: opaque // ignore: cast_nullable_to_non_nullable
|
||||
as String,
|
||||
),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
/// @nodoc
|
||||
@JsonSerializable()
|
||||
class _$AuthDigestModelImpl implements _AuthDigestModel {
|
||||
const _$AuthDigestModelImpl({
|
||||
required this.username,
|
||||
required this.password,
|
||||
required this.realm,
|
||||
required this.nonce,
|
||||
required this.algorithm,
|
||||
required this.qop,
|
||||
required this.opaque,
|
||||
});
|
||||
|
||||
factory _$AuthDigestModelImpl.fromJson(Map<String, dynamic> json) =>
|
||||
_$$AuthDigestModelImplFromJson(json);
|
||||
|
||||
@override
|
||||
final String username;
|
||||
@override
|
||||
final String password;
|
||||
@override
|
||||
final String realm;
|
||||
@override
|
||||
final String nonce;
|
||||
@override
|
||||
final String algorithm;
|
||||
@override
|
||||
final String qop;
|
||||
@override
|
||||
final String opaque;
|
||||
|
||||
@override
|
||||
String toString() {
|
||||
return 'AuthDigestModel(username: $username, password: $password, realm: $realm, nonce: $nonce, algorithm: $algorithm, qop: $qop, opaque: $opaque)';
|
||||
}
|
||||
|
||||
@override
|
||||
bool operator ==(Object other) {
|
||||
return identical(this, other) ||
|
||||
(other.runtimeType == runtimeType &&
|
||||
other is _$AuthDigestModelImpl &&
|
||||
(identical(other.username, username) ||
|
||||
other.username == username) &&
|
||||
(identical(other.password, password) ||
|
||||
other.password == password) &&
|
||||
(identical(other.realm, realm) || other.realm == realm) &&
|
||||
(identical(other.nonce, nonce) || other.nonce == nonce) &&
|
||||
(identical(other.algorithm, algorithm) ||
|
||||
other.algorithm == algorithm) &&
|
||||
(identical(other.qop, qop) || other.qop == qop) &&
|
||||
(identical(other.opaque, opaque) || other.opaque == opaque));
|
||||
}
|
||||
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@override
|
||||
int get hashCode => Object.hash(
|
||||
runtimeType,
|
||||
username,
|
||||
password,
|
||||
realm,
|
||||
nonce,
|
||||
algorithm,
|
||||
qop,
|
||||
opaque,
|
||||
);
|
||||
|
||||
/// Create a copy of AuthDigestModel
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
@override
|
||||
@pragma('vm:prefer-inline')
|
||||
_$$AuthDigestModelImplCopyWith<_$AuthDigestModelImpl> get copyWith =>
|
||||
__$$AuthDigestModelImplCopyWithImpl<_$AuthDigestModelImpl>(
|
||||
this,
|
||||
_$identity,
|
||||
);
|
||||
|
||||
@override
|
||||
Map<String, dynamic> toJson() {
|
||||
return _$$AuthDigestModelImplToJson(this);
|
||||
}
|
||||
}
|
||||
|
||||
abstract class _AuthDigestModel implements AuthDigestModel {
|
||||
const factory _AuthDigestModel({
|
||||
required final String username,
|
||||
required final String password,
|
||||
required final String realm,
|
||||
required final String nonce,
|
||||
required final String algorithm,
|
||||
required final String qop,
|
||||
required final String opaque,
|
||||
}) = _$AuthDigestModelImpl;
|
||||
|
||||
factory _AuthDigestModel.fromJson(Map<String, dynamic> json) =
|
||||
_$AuthDigestModelImpl.fromJson;
|
||||
|
||||
@override
|
||||
String get username;
|
||||
@override
|
||||
String get password;
|
||||
@override
|
||||
String get realm;
|
||||
@override
|
||||
String get nonce;
|
||||
@override
|
||||
String get algorithm;
|
||||
@override
|
||||
String get qop;
|
||||
@override
|
||||
String get opaque;
|
||||
|
||||
/// Create a copy of AuthDigestModel
|
||||
/// with the given fields replaced by the non-null parameter values.
|
||||
@override
|
||||
@JsonKey(includeFromJson: false, includeToJson: false)
|
||||
_$$AuthDigestModelImplCopyWith<_$AuthDigestModelImpl> get copyWith =>
|
||||
throw _privateConstructorUsedError;
|
||||
}
|
||||
@@ -0,0 +1,31 @@
|
||||
// GENERATED CODE - DO NOT MODIFY BY HAND
|
||||
|
||||
part of 'auth_digest_model.dart';
|
||||
|
||||
// **************************************************************************
|
||||
// JsonSerializableGenerator
|
||||
// **************************************************************************
|
||||
|
||||
_$AuthDigestModelImpl _$$AuthDigestModelImplFromJson(
|
||||
Map<String, dynamic> json,
|
||||
) => _$AuthDigestModelImpl(
|
||||
username: json['username'] as String,
|
||||
password: json['password'] as String,
|
||||
realm: json['realm'] as String,
|
||||
nonce: json['nonce'] as String,
|
||||
algorithm: json['algorithm'] as String,
|
||||
qop: json['qop'] as String,
|
||||
opaque: json['opaque'] as String,
|
||||
);
|
||||
|
||||
Map<String, dynamic> _$$AuthDigestModelImplToJson(
|
||||
_$AuthDigestModelImpl instance,
|
||||
) => <String, dynamic>{
|
||||
'username': instance.username,
|
||||
'password': instance.password,
|
||||
'realm': instance.realm,
|
||||
'nonce': instance.nonce,
|
||||
'algorithm': instance.algorithm,
|
||||
'qop': instance.qop,
|
||||
'opaque': instance.opaque,
|
||||
};
|
||||
@@ -5,3 +5,4 @@ export 'auth/auth_api_key_model.dart';
|
||||
export 'auth/auth_basic_model.dart';
|
||||
export 'auth/auth_bearer_model.dart';
|
||||
export 'auth/auth_jwt_model.dart';
|
||||
export 'auth/auth_digest_model.dart';
|
||||
|
||||
@@ -20,14 +20,22 @@ Future<(HttpResponse?, Duration?, String?)> sendHttpRequest(
|
||||
HttpRequestModel requestModel, {
|
||||
SupportedUriSchemes defaultUriScheme = kDefaultUriScheme,
|
||||
bool noSSL = false,
|
||||
bool enableAuth = true,
|
||||
}) async {
|
||||
if (httpClientManager.wasRequestCancelled(requestId)) {
|
||||
httpClientManager.removeCancelledRequest(requestId);
|
||||
}
|
||||
final client = httpClientManager.createClient(requestId, noSSL: noSSL);
|
||||
|
||||
// Handle authentication
|
||||
final authenticatedRequestModel = handleAuth(requestModel, authData);
|
||||
HttpRequestModel authenticatedRequestModel = requestModel.copyWith();
|
||||
|
||||
try {
|
||||
if (enableAuth) {
|
||||
authenticatedRequestModel = await handleAuth(requestModel, authData);
|
||||
}
|
||||
} catch (e) {
|
||||
return (null, null, e.toString());
|
||||
}
|
||||
|
||||
(Uri?, String?) uriRec = getValidRequestUri(
|
||||
authenticatedRequestModel.url,
|
||||
|
||||
237
packages/better_networking/lib/utils/auth/digest_auth_utils.dart
Normal file
237
packages/better_networking/lib/utils/auth/digest_auth_utils.dart
Normal file
@@ -0,0 +1,237 @@
|
||||
import 'dart:convert';
|
||||
import 'dart:math' as math;
|
||||
|
||||
import 'package:convert/convert.dart';
|
||||
import 'package:crypto/crypto.dart' as crypto;
|
||||
import '../../models/auth/auth_digest_model.dart';
|
||||
import '../../models/models.dart';
|
||||
|
||||
Map<String, String>? splitAuthenticateHeader(String header) {
|
||||
if (!header.startsWith('Digest ')) {
|
||||
return null;
|
||||
}
|
||||
header = header.substring(7); // remove 'Digest '
|
||||
|
||||
var ret = <String, String>{};
|
||||
|
||||
final components = header.split(',').map((token) => token.trim());
|
||||
for (var component in components) {
|
||||
final kv = component.split('=');
|
||||
ret[kv[0]] = kv.getRange(1, kv.length).join('=').replaceAll('"', '');
|
||||
}
|
||||
return ret;
|
||||
}
|
||||
|
||||
String sha256Hash(String data) {
|
||||
var content = const Utf8Encoder().convert(data);
|
||||
var sha256 = crypto.sha256;
|
||||
var digest = sha256.convert(content).toString();
|
||||
return digest;
|
||||
}
|
||||
|
||||
String md5Hash(String data) {
|
||||
var content = const Utf8Encoder().convert(data);
|
||||
var md5 = crypto.md5;
|
||||
var digest = md5.convert(content).toString();
|
||||
return digest;
|
||||
}
|
||||
|
||||
String _formatNonceCount(int nc) {
|
||||
return nc.toRadixString(16).padLeft(8, '0');
|
||||
}
|
||||
|
||||
String _computeHA1(
|
||||
String realm,
|
||||
String? algorithm,
|
||||
String username,
|
||||
String password,
|
||||
String? nonce,
|
||||
String? cnonce,
|
||||
) {
|
||||
if (algorithm == 'MD5') {
|
||||
final token1 = '$username:$realm:$password';
|
||||
return md5Hash(token1);
|
||||
} else if (algorithm == 'MD5-sess') {
|
||||
final token1 = '$username:$realm:$password';
|
||||
final md51 = md5Hash(token1);
|
||||
final token2 = '$md51:$nonce:$cnonce';
|
||||
return md5Hash(token2);
|
||||
} else if (algorithm == 'SHA-256') {
|
||||
final token1 = '$username:$realm:$password';
|
||||
return sha256Hash(token1);
|
||||
} else if (algorithm == 'SHA-256-sess') {
|
||||
final token1 = '$username:$realm:$password';
|
||||
final sha256_1 = sha256Hash(token1);
|
||||
final token2 = '$sha256_1:$nonce:$cnonce';
|
||||
return sha256Hash(token2);
|
||||
} else {
|
||||
throw ArgumentError.value(algorithm, 'algorithm', 'Unsupported algorithm');
|
||||
}
|
||||
}
|
||||
|
||||
Map<String, String?> computeResponse(
|
||||
String method,
|
||||
String path,
|
||||
String body,
|
||||
String? algorithm,
|
||||
String? qop,
|
||||
String? opaque,
|
||||
String realm,
|
||||
String? cnonce,
|
||||
String? nonce,
|
||||
int nc,
|
||||
String username,
|
||||
String password,
|
||||
) {
|
||||
var ret = <String, String?>{};
|
||||
|
||||
algorithm ??= 'MD5';
|
||||
final ha1 = _computeHA1(realm, algorithm, username, password, nonce, cnonce);
|
||||
|
||||
String ha2;
|
||||
|
||||
if (algorithm.startsWith('MD5')) {
|
||||
if (qop == 'auth-int') {
|
||||
final bodyHash = md5Hash(body);
|
||||
final token2 = '$method:$path:$bodyHash';
|
||||
ha2 = md5Hash(token2);
|
||||
} else {
|
||||
// qop in [null, auth]
|
||||
final token2 = '$method:$path';
|
||||
ha2 = md5Hash(token2);
|
||||
}
|
||||
} else {
|
||||
if (qop == 'auth-int') {
|
||||
final bodyHash = sha256Hash(body);
|
||||
final token2 = '$method:$path:$bodyHash';
|
||||
ha2 = sha256Hash(token2);
|
||||
} else {
|
||||
// qop in [null, auth]
|
||||
final token2 = '$method:$path';
|
||||
ha2 = sha256Hash(token2);
|
||||
}
|
||||
}
|
||||
|
||||
final nonceCount = _formatNonceCount(nc);
|
||||
ret['username'] = username;
|
||||
ret['realm'] = realm;
|
||||
ret['nonce'] = nonce;
|
||||
ret['uri'] = path;
|
||||
if (qop != null) {
|
||||
ret['qop'] = qop;
|
||||
}
|
||||
ret['nc'] = nonceCount;
|
||||
ret['cnonce'] = cnonce;
|
||||
if (opaque != null) {
|
||||
ret['opaque'] = opaque;
|
||||
}
|
||||
ret['algorithm'] = algorithm;
|
||||
|
||||
if (algorithm.startsWith('MD5')) {
|
||||
if (qop == null) {
|
||||
final token3 = '$ha1:$nonce:$ha2';
|
||||
ret['response'] = md5Hash(token3);
|
||||
} else if (qop == 'auth' || qop == 'auth-int') {
|
||||
final token3 = '$ha1:$nonce:$nonceCount:$cnonce:$qop:$ha2';
|
||||
ret['response'] = md5Hash(token3);
|
||||
}
|
||||
} else {
|
||||
if (qop == null) {
|
||||
final token3 = '$ha1:$nonce:$ha2';
|
||||
ret['response'] = sha256Hash(token3);
|
||||
} else if (qop == 'auth' || qop == 'auth-int') {
|
||||
final token3 = '$ha1:$nonce:$nonceCount:$cnonce:$qop:$ha2';
|
||||
ret['response'] = sha256Hash(token3);
|
||||
}
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
class DigestAuth {
|
||||
String username;
|
||||
String password;
|
||||
|
||||
// must get from first response
|
||||
String? _algorithm;
|
||||
String? _qop;
|
||||
String? _realm;
|
||||
String? _nonce;
|
||||
String? _opaque;
|
||||
|
||||
int _nc = 0; // request counter
|
||||
|
||||
DigestAuth(this.username, this.password);
|
||||
|
||||
// Constructor that takes an AuthDigestModel
|
||||
DigestAuth.fromModel(AuthDigestModel model)
|
||||
: username = model.username,
|
||||
password = model.password,
|
||||
_realm = model.realm,
|
||||
_nonce = model.nonce,
|
||||
_algorithm = model.algorithm,
|
||||
_qop = model.qop,
|
||||
_opaque = model.opaque.isNotEmpty ? model.opaque : null;
|
||||
|
||||
String _computeNonce() {
|
||||
final rnd = math.Random.secure();
|
||||
|
||||
final values = List<int>.generate(16, (i) => rnd.nextInt(256));
|
||||
|
||||
return hex.encode(values);
|
||||
}
|
||||
|
||||
String getAuthString(HttpRequestModel res) {
|
||||
final cnonce = _computeNonce();
|
||||
final url = Uri.parse(res.url);
|
||||
final method = res.method.name.toUpperCase();
|
||||
final body = res.body ?? '';
|
||||
_nc += 1;
|
||||
// if url has query parameters, append query to path
|
||||
final path = url.hasQuery ? '${url.path}?${url.query}' : url.path;
|
||||
|
||||
// after the first request we have the nonce, so we can provide credentials
|
||||
final authValues = computeResponse(
|
||||
method,
|
||||
path,
|
||||
body,
|
||||
_algorithm,
|
||||
_qop,
|
||||
_opaque,
|
||||
_realm!,
|
||||
cnonce,
|
||||
_nonce,
|
||||
_nc,
|
||||
username,
|
||||
password,
|
||||
);
|
||||
final authValuesString = authValues.entries
|
||||
.where((e) => e.value != null)
|
||||
.map(
|
||||
(e) => [
|
||||
e.key,
|
||||
'=',
|
||||
['algorithm', 'qop', 'nc'].contains(e.key) ? '' : '"',
|
||||
e.value,
|
||||
['algorithm', 'qop', 'nc'].contains(e.key) ? '' : '"',
|
||||
].join(''),
|
||||
)
|
||||
.toList()
|
||||
.join(', ');
|
||||
final authString = 'Digest $authValuesString';
|
||||
return authString;
|
||||
}
|
||||
|
||||
// TODO: Use this function
|
||||
void initFromAuthenticateHeader(String /*!*/ authInfo) {
|
||||
final values = splitAuthenticateHeader(authInfo);
|
||||
if (values != null) {
|
||||
_algorithm = values['algorithm'] ?? _algorithm;
|
||||
_qop = values['qop'] ?? _qop;
|
||||
_realm = values['realm'] ?? _realm;
|
||||
_nonce = values['nonce'] ?? _nonce;
|
||||
_opaque = values['opaque'] ?? _opaque;
|
||||
_nc = 0;
|
||||
}
|
||||
}
|
||||
}
|
||||
175
packages/better_networking/lib/utils/auth/handle_auth.dart
Normal file
175
packages/better_networking/lib/utils/auth/handle_auth.dart
Normal file
@@ -0,0 +1,175 @@
|
||||
import 'dart:convert';
|
||||
import 'dart:math';
|
||||
import 'package:better_networking/utils/auth/jwt_auth_utils.dart';
|
||||
import 'package:better_networking/utils/auth/digest_auth_utils.dart';
|
||||
import 'package:better_networking/better_networking.dart';
|
||||
|
||||
Future<HttpRequestModel> handleAuth(
|
||||
HttpRequestModel httpRequestModel,
|
||||
AuthModel? authData,
|
||||
) async {
|
||||
if (authData == null || authData.type == APIAuthType.none) {
|
||||
return httpRequestModel;
|
||||
}
|
||||
|
||||
List<NameValueModel> updatedHeaders = List.from(
|
||||
httpRequestModel.headers ?? [],
|
||||
);
|
||||
List<NameValueModel> updatedParams = List.from(httpRequestModel.params ?? []);
|
||||
List<bool> updatedHeaderEnabledList = List.from(
|
||||
httpRequestModel.isHeaderEnabledList ?? [],
|
||||
);
|
||||
List<bool> updatedParamEnabledList = List.from(
|
||||
httpRequestModel.isParamEnabledList ?? [],
|
||||
);
|
||||
|
||||
switch (authData.type) {
|
||||
case APIAuthType.basic:
|
||||
if (authData.basic != null) {
|
||||
final basicAuth = authData.basic!;
|
||||
final encoded = base64Encode(
|
||||
utf8.encode('${basicAuth.username}:${basicAuth.password}'),
|
||||
);
|
||||
updatedHeaders.add(
|
||||
NameValueModel(name: 'Authorization', value: 'Basic $encoded'),
|
||||
);
|
||||
updatedHeaderEnabledList.add(true);
|
||||
}
|
||||
break;
|
||||
|
||||
case APIAuthType.bearer:
|
||||
if (authData.bearer != null) {
|
||||
final bearerAuth = authData.bearer!;
|
||||
updatedHeaders.add(
|
||||
NameValueModel(
|
||||
name: 'Authorization',
|
||||
value: 'Bearer ${bearerAuth.token}',
|
||||
),
|
||||
);
|
||||
updatedHeaderEnabledList.add(true);
|
||||
}
|
||||
break;
|
||||
|
||||
case APIAuthType.jwt:
|
||||
if (authData.jwt != null) {
|
||||
final jwtAuth = authData.jwt!;
|
||||
|
||||
// Generate JWT token
|
||||
final jwtToken = generateJWT(jwtAuth);
|
||||
|
||||
if (jwtAuth.addTokenTo == 'header') {
|
||||
// Add to request header with prefix
|
||||
final headerValue = jwtAuth.headerPrefix.isNotEmpty
|
||||
? '${jwtAuth.headerPrefix} $jwtToken'
|
||||
: jwtToken;
|
||||
updatedHeaders.add(
|
||||
NameValueModel(name: 'Authorization', value: headerValue),
|
||||
);
|
||||
updatedHeaderEnabledList.add(true);
|
||||
} else if (jwtAuth.addTokenTo == 'query') {
|
||||
// Add to query parameters(if selected)
|
||||
final paramKey = jwtAuth.queryParamKey.isNotEmpty
|
||||
? jwtAuth.queryParamKey
|
||||
: 'token';
|
||||
updatedParams.add(NameValueModel(name: paramKey, value: jwtToken));
|
||||
updatedParamEnabledList.add(true);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case APIAuthType.apiKey:
|
||||
if (authData.apikey != null) {
|
||||
final apiKeyAuth = authData.apikey!;
|
||||
if (apiKeyAuth.location == 'header') {
|
||||
updatedHeaders.add(
|
||||
NameValueModel(name: apiKeyAuth.name, value: apiKeyAuth.key),
|
||||
);
|
||||
updatedHeaderEnabledList.add(true);
|
||||
} else if (apiKeyAuth.location == 'query') {
|
||||
updatedParams.add(
|
||||
NameValueModel(name: apiKeyAuth.name, value: apiKeyAuth.key),
|
||||
);
|
||||
updatedParamEnabledList.add(true);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case APIAuthType.none:
|
||||
break;
|
||||
case APIAuthType.digest:
|
||||
if (authData.digest != null) {
|
||||
final digestAuthModel = authData.digest!;
|
||||
|
||||
if (digestAuthModel.realm.isNotEmpty &&
|
||||
digestAuthModel.nonce.isNotEmpty) {
|
||||
final digestAuth = DigestAuth.fromModel(digestAuthModel);
|
||||
final authString = digestAuth.getAuthString(httpRequestModel);
|
||||
|
||||
updatedHeaders.add(
|
||||
NameValueModel(name: 'Authorization', value: authString),
|
||||
);
|
||||
updatedHeaderEnabledList.add(true);
|
||||
} else {
|
||||
final httpResult = await sendHttpRequest(
|
||||
"digest-${Random.secure()}",
|
||||
APIType.rest,
|
||||
authData,
|
||||
httpRequestModel,
|
||||
enableAuth: false,
|
||||
);
|
||||
final httpResponse = httpResult.$1;
|
||||
|
||||
if (httpResponse == null) {
|
||||
throw Exception("Initial Digest request failed: no response");
|
||||
}
|
||||
|
||||
if (httpResponse.statusCode == 401) {
|
||||
final wwwAuthHeader = httpResponse.headers[kHeaderWwwAuthenticate];
|
||||
|
||||
if (wwwAuthHeader == null) {
|
||||
throw Exception("401 response missing www-authenticate header");
|
||||
}
|
||||
|
||||
final authParams = splitAuthenticateHeader(wwwAuthHeader);
|
||||
|
||||
if (authParams == null) {
|
||||
throw Exception("Invalid Digest header format");
|
||||
}
|
||||
|
||||
final updatedDigestModel = digestAuthModel.copyWith(
|
||||
realm: authParams['realm'] ?? '',
|
||||
nonce: authParams['nonce'] ?? '',
|
||||
algorithm: authParams['algorithm'] ?? 'MD5',
|
||||
qop: authParams['qop'] ?? 'auth',
|
||||
opaque: authParams['opaque'] ?? '',
|
||||
);
|
||||
|
||||
final digestAuth = DigestAuth.fromModel(updatedDigestModel);
|
||||
final authString = digestAuth.getAuthString(httpRequestModel);
|
||||
updatedHeaders.add(
|
||||
NameValueModel(name: 'Authorization', value: authString),
|
||||
);
|
||||
updatedHeaderEnabledList.add(true);
|
||||
} else {
|
||||
throw Exception(
|
||||
"Initial Digest request failed due to unexpected status code: ${httpResponse.body}. Status Code: ${httpResponse.statusCode}",
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
break;
|
||||
case APIAuthType.oauth1:
|
||||
// TODO: Handle this case.
|
||||
throw UnimplementedError();
|
||||
case APIAuthType.oauth2:
|
||||
// TODO: Handle this case.
|
||||
throw UnimplementedError();
|
||||
}
|
||||
|
||||
return httpRequestModel.copyWith(
|
||||
headers: updatedHeaders,
|
||||
params: updatedParams,
|
||||
isHeaderEnabledList: updatedHeaderEnabledList,
|
||||
isParamEnabledList: updatedParamEnabledList,
|
||||
);
|
||||
}
|
||||
@@ -1,101 +0,0 @@
|
||||
import 'dart:convert';
|
||||
import 'package:better_networking/utils/auth_utils.dart';
|
||||
import 'package:better_networking/better_networking.dart';
|
||||
|
||||
import '../models/auth/api_auth_model.dart';
|
||||
|
||||
HttpRequestModel handleAuth(HttpRequestModel httpRequestModel,AuthModel? authData) {
|
||||
if (authData == null || authData.type == APIAuthType.none) {
|
||||
return httpRequestModel;
|
||||
}
|
||||
|
||||
List<NameValueModel> updatedHeaders =
|
||||
List.from(httpRequestModel.headers ?? []);
|
||||
List<NameValueModel> updatedParams = List.from(httpRequestModel.params ?? []);
|
||||
List<bool> updatedHeaderEnabledList =
|
||||
List.from(httpRequestModel.isHeaderEnabledList ?? []);
|
||||
List<bool> updatedParamEnabledList =
|
||||
List.from(httpRequestModel.isParamEnabledList ?? []);
|
||||
|
||||
switch (authData.type) {
|
||||
case APIAuthType.basic:
|
||||
if (authData.basic != null) {
|
||||
final basicAuth = authData.basic!;
|
||||
final encoded = base64Encode(
|
||||
utf8.encode('${basicAuth.username}:${basicAuth.password}'));
|
||||
updatedHeaders.add(
|
||||
NameValueModel(name: 'Authorization', value: 'Basic $encoded'));
|
||||
updatedHeaderEnabledList.add(true);
|
||||
}
|
||||
break;
|
||||
|
||||
case APIAuthType.bearer:
|
||||
if (authData.bearer != null) {
|
||||
final bearerAuth = authData.bearer!;
|
||||
updatedHeaders.add(NameValueModel(
|
||||
name: 'Authorization', value: 'Bearer ${bearerAuth.token}'));
|
||||
updatedHeaderEnabledList.add(true);
|
||||
}
|
||||
break;
|
||||
|
||||
case APIAuthType.jwt:
|
||||
if (authData.jwt != null) {
|
||||
final jwtAuth = authData.jwt!;
|
||||
|
||||
// Generate JWT token
|
||||
final jwtToken = generateJWT(jwtAuth);
|
||||
|
||||
if (jwtAuth.addTokenTo == 'header') {
|
||||
// Add to request header with prefix
|
||||
final headerValue = jwtAuth.headerPrefix.isNotEmpty
|
||||
? '${jwtAuth.headerPrefix} $jwtToken'
|
||||
: jwtToken;
|
||||
updatedHeaders
|
||||
.add(NameValueModel(name: 'Authorization', value: headerValue));
|
||||
updatedHeaderEnabledList.add(true);
|
||||
} else if (jwtAuth.addTokenTo == 'query') {
|
||||
// Add to query parameters(if selected)
|
||||
final paramKey = jwtAuth.queryParamKey.isNotEmpty
|
||||
? jwtAuth.queryParamKey
|
||||
: 'token';
|
||||
updatedParams.add(NameValueModel(name: paramKey, value: jwtToken));
|
||||
updatedParamEnabledList.add(true);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case APIAuthType.apiKey:
|
||||
if (authData.apikey != null) {
|
||||
final apiKeyAuth = authData.apikey!;
|
||||
if (apiKeyAuth.location == 'header') {
|
||||
updatedHeaders.add(
|
||||
NameValueModel(name: apiKeyAuth.name, value: apiKeyAuth.key));
|
||||
updatedHeaderEnabledList.add(true);
|
||||
} else if (apiKeyAuth.location == 'query') {
|
||||
updatedParams.add(
|
||||
NameValueModel(name: apiKeyAuth.name, value: apiKeyAuth.key));
|
||||
updatedParamEnabledList.add(true);
|
||||
}
|
||||
}
|
||||
break;
|
||||
|
||||
case APIAuthType.none:
|
||||
break;
|
||||
case APIAuthType.digest:
|
||||
// TODO: Handle this case.
|
||||
throw UnimplementedError();
|
||||
case APIAuthType.oauth1:
|
||||
// TODO: Handle this case.
|
||||
throw UnimplementedError();
|
||||
case APIAuthType.oauth2:
|
||||
// TODO: Handle this case.
|
||||
throw UnimplementedError();
|
||||
}
|
||||
|
||||
return httpRequestModel.copyWith(
|
||||
headers: updatedHeaders,
|
||||
params: updatedParams,
|
||||
isHeaderEnabledList: updatedHeaderEnabledList,
|
||||
isParamEnabledList: updatedParamEnabledList,
|
||||
);
|
||||
}
|
||||
@@ -4,4 +4,4 @@ export 'http_request_utils.dart';
|
||||
export 'http_response_utils.dart';
|
||||
export 'string_utils.dart' hide RandomStringGenerator;
|
||||
export 'uri_utils.dart';
|
||||
export 'handle_auth.dart';
|
||||
export 'auth/handle_auth.dart';
|
||||
|
||||
@@ -26,6 +26,7 @@ dependencies:
|
||||
seed: ^0.0.3
|
||||
xml: ^6.3.0
|
||||
crypto: ^3.0.6
|
||||
convert: ^3.1.2
|
||||
|
||||
dev_dependencies:
|
||||
flutter_test:
|
||||
|
||||
Reference in New Issue
Block a user