LCOV - code coverage report
Current view: top level - lib/src/auth - agattp_auth_digest.dart (source / functions) Coverage Total Hit
Test: agattp Lines: 98.4 % 62 61
Test Date: 2025-01-28 00:11:13 Functions: - 0 0

            Line data    Source code
       1              : import 'dart:convert';
       2              : import 'dart:io';
       3              : 
       4              : import 'package:agattp/src/agattp.dart';
       5              : import 'package:agattp/src/agattp_method.dart';
       6              : import 'package:agattp/src/agattp_response.dart';
       7              : import 'package:agattp/src/agattp_utils.dart';
       8              : import 'package:agattp/src/auth/agattp_abstract_auth.dart';
       9              : import 'package:crypto/crypto.dart';
      10              : 
      11              : /// A Digest can use either md5 or sha256 algorithms to communicate. This enum
      12              : /// encodes both of those options, with a helper factory that returns the
      13              : /// appropriate algorithm based on a given value from the digest payload
      14              : enum DigestAlgorithm {
      15              :   md5,
      16              :   sha256;
      17              : 
      18              :   /// Parse the given value and identify which algorithm it correlates with.
      19              :   /// Will default to md5 if the parsing fails for any reason
      20            1 :   factory DigestAlgorithm.parse(dynamic value) {
      21              :     final String name =
      22            4 :         value.toString().toLowerCase().replaceAll(RegExp(r'-|\s|_'), '');
      23              : 
      24            1 :     return values.firstWhere(
      25            3 :       (DigestAlgorithm alg) => alg.name == name,
      26            0 :       orElse: () => DigestAlgorithm.md5,
      27              :     );
      28              :   }
      29              : }
      30              : 
      31              : /// Implementation of the Digest authentication strategy
      32              : class AgattpAuthDigest implements AgattpAuthInterface {
      33              :   /// The digest username to be used in authentication
      34              :   final String username;
      35              : 
      36              :   /// The digest password to be used in authentication
      37              :   final String password;
      38              : 
      39              :   /// Stores misc data for the digest algorithm
      40              :   final Map<String, String> map = <String, String>{};
      41              : 
      42              :   bool _ready = false;
      43              :   int _nc = 0;
      44              :   late String _realm;
      45              :   late String _nonce;
      46              :   late String _qop;
      47              :   late String _cnonce;
      48              :   late DigestAlgorithm _algorithm;
      49              : 
      50            1 :   AgattpAuthDigest({
      51              :     required this.username,
      52              :     required this.password,
      53            1 :   }) : super();
      54              : 
      55            1 :   void reset() {
      56            1 :     _ready = false;
      57            1 :     _nc = 0;
      58              :   }
      59              : 
      60            2 :   bool get ready => _ready;
      61              : 
      62            1 :   @override
      63              :   Future<Map<String, String>> getAuthHeaders(
      64              :     AgattpMethod method,
      65              :     Uri uri,
      66              :   ) async {
      67            1 :     if (!_ready) {
      68            2 :       final AgattpResponse response = await Agattp().get(uri);
      69              : 
      70              :       final String wwwAuthenticate =
      71            2 :           response.headers.value(HttpHeaders.wwwAuthenticateHeader) ?? '';
      72              : 
      73            1 :       if (wwwAuthenticate.isEmpty) {
      74            1 :         throw Exception('WWW-Authenticate header not found');
      75              :       }
      76              : 
      77              :       final List<String> parts =
      78            3 :           wwwAuthenticate.replaceAll(RegExp('^[Dd]igest'), '').split(',');
      79              : 
      80            2 :       for (final String part in parts) {
      81            2 :         final List<String> p2 = Utils.splitFirst(part.trim(), '=');
      82            2 :         if (p2.length != 2) {
      83              :           continue;
      84              :         }
      85              : 
      86            6 :         map[p2.first.trim()] = p2.last.trim();
      87              :       }
      88              : 
      89            2 :       map.remove('stale');
      90              : 
      91            4 :       _algorithm = DigestAlgorithm.parse(map['algorithm']);
      92              : 
      93            3 :       _realm = map['realm'] ?? '';
      94              : 
      95            3 :       _nonce = map['nonce'] ?? '';
      96              : 
      97            3 :       _qop = map['qop'] ?? '';
      98              : 
      99            1 :       _cnonce =
     100            5 :           md5.convert(DateTime.now().toIso8601String().codeUnits).toString();
     101              : 
     102            1 :       _ready = true;
     103              :     }
     104              : 
     105            4 :     map['username'] = '"$username"';
     106              : 
     107            4 :     map['uri'] = '"${uri.path}"';
     108              : 
     109            3 :     map['realm'] = _realm;
     110              : 
     111            3 :     map['nonce'] = _nonce;
     112              : 
     113            3 :     map['qop'] = _qop;
     114              : 
     115            4 :     map['cnonce'] = '"$_cnonce"';
     116              : 
     117            2 :     _nc++;
     118              : 
     119            5 :     map['nc'] = _nc.toString().padLeft(8, '0');
     120              : 
     121            3 :     map['response'] = '"${_getResponse(
     122            1 :       username: username,
     123            2 :       realm: Utils.removeQuotes(_realm),
     124            1 :       password: password,
     125            2 :       method: method.name.toUpperCase(),
     126            1 :       path: uri.path,
     127            2 :       nonce: Utils.removeQuotes(_nonce),
     128            2 :       nc: map['nc']!,
     129            1 :       cnonce: _cnonce,
     130            2 :       qop: Utils.removeQuotes(_qop),
     131            1 :     )}"';
     132              : 
     133            3 :     final String token = map.entries.map(
     134            4 :       (MapEntry<String, String> e) => '${e.key}=${e.value}',
     135            1 :     ).join(', ');
     136              : 
     137            2 :     return <String, String>{HttpHeaders.authorizationHeader: 'Digest $token'};
     138              :   }
     139              : 
     140            1 :   String _getResponse({
     141              :     required String username,
     142              :     required String realm,
     143              :     required String password,
     144              :     required String method,
     145              :     required String path,
     146              :     required String nonce,
     147              :     required String nc,
     148              :     required String cnonce,
     149              :     required String qop,
     150              :   }) {
     151            1 :     final Hash hash = switch (_algorithm) {
     152            1 :       DigestAlgorithm.md5 => md5,
     153            1 :       DigestAlgorithm.sha256 => sha256,
     154              :     };
     155              : 
     156              :     final String h1 =
     157            4 :         hash.convert(utf8.encode('$username:$realm:$password')).toString();
     158              : 
     159            4 :     final String h2 = hash.convert(utf8.encode('$method:$path')).toString();
     160              : 
     161            1 :     return hash.convert(
     162            2 :       utf8.encode('$h1:$nonce:$nc:$cnonce:$qop:$h2'),
     163            1 :     ).toString();
     164              :   }
     165              : }
        

Generated by: LCOV version 2.0-1