### ----------------------------------------------------
### -- Auth::SCRAM::Async
### -- Licenses: Artistic-2.0
### -- Authors: Leon Timmermans
### -- File: t/01-client.rakutest
### ----------------------------------------------------
use Test;
 
use Auth::SCRAM::Async;
use MIME::Base64;

sub get-client(Str :$username = 'user', Str :$password = 'pencil', :$digest = Auth::SCRAM::Async::SHA1, |other) {
	Auth::SCRAM::Async::Client.new(:$username, :$password, :$digest, |other);
}

subtest 'constructors' => sub {
	my $client = get-client;
	is($client.digest,      Auth::SCRAM::Async::SHA1,  'default digest');
	is($client.username,  'user',   'username attribute');
	is($client.password,  'pencil', 'password attribute');
	is($client.nonce-bits, 192,      'nonce size attribute');
 
	for (Auth::SCRAM::Async::SHA1, Auth::SCRAM::Async::SHA256, Auth::SCRAM::Async::SHA384, Auth::SCRAM::Async::SHA512) -> $digest {
		my $obj = get-client(:$digest);
		is( $obj.digest, $digest, "digest set correctly to $digest.key()" );
	}
}
 
subtest 'client first message' => sub {
	my $client = get-client;
	my $first = $client.first-message;
	like($first, rx{ ^ 'n,,n=user,r=' <[a..zA..Z0..9+/=]>**32 $ }, 'message structure');
 
	like(get-client(:username('us,e=r')).first-message, rx{ ^ 'n,,n=us=2ce=3dr' }, 'user name , and = encoding' );
 
	like(get-client(:authorization-id('other,me')).first-message, rx{ ^ 'n,a=other=2cme,n=user,r=' .+ }, 'authorization-id with encoding');
}

my $client-nonce-encoded ='fyko+d2lbbFgONRv9qkxdawL';
my $client-nonce = MIME::Base64.decode($client-nonce-encoded);

subtest 'RFC 5802 example' => sub {
	# force client nonce to match RFC5802 example
	my $client = get-client( :$client-nonce );
	my $first = $client.first-message;
	is($first, 'n,,n=user,r=fyko+d2lbbFgONRv9qkxdawL', 'client first message' );
 
	# RFC5802 example server-first-message
	my $server-first = 'r=fyko+d2lbbFgONRv9qkxdawL3rfcNHYJY1ZVvWVs7j,s=QSXCR+Q6sek8bf92,i=4096';
	is(
		$client.final-message($server-first),
		'c=biws,r=fyko+d2lbbFgONRv9qkxdawL3rfcNHYJY1ZVvWVs7j,p=v0X8v3Bz2T0CJGbJQyF0X+HI4Ts=',
		'client final message'
	);
 
	ok( $client.validate('v=rmF9pqV8S7suAoZWja4dJRkFsKQ='), 'server message validated' );
}
 
subtest 'RFC 7677 example (SHA256)' => sub {
	# force client nonce to match RFC7677 example
	my $client = get-client(:digest(Auth::SCRAM::Async::SHA256), :client-nonce(MIME::Base64.decode('rOprNGfwEbeRWgbNEkqO')));
	my $first = $client.first-message();
	is($first, 'n,,n=user,r=rOprNGfwEbeRWgbNEkqO', 'client first message' );
 
	# RFC7677 example server-first-message
	my $server-first = 'r=rOprNGfwEbeRWgbNEkqO%hvYDpWUa2RaTCAfuxFIlj)hNlF$k0,s=W22ZaJ0SNY7soEsUEjb6gQ==,i=4096';
	is(
		$client.final-message($server-first),
		'c=biws,r=rOprNGfwEbeRWgbNEkqO%hvYDpWUa2RaTCAfuxFIlj)hNlF$k0,p=dHzbZapWIk4jUhN+Ute9ytag9zjfMHgsqmmiz7AndVQ=',
		'client final message'
	);
 
	ok( $client.validate('v=6rriTRBi23WpRR/wtup+mMhUZUn/dB5nLTJRsjl95G4='), 'server message validated' );
 
}
 
subtest 'Unicode username' => sub {
	my $client = get-client(
		:username("ram\x[F3]n"),
		:password("p\x[C5]ssword"),
		:client-nonce(MIME::Base64.decode('cT4Z0nGchlcAXXkDBrYFlC7b3bXA24xr'))
	);
	is(
		$client.first-message,
		"n,,n=ram\x[F3]n,r=cT4Z0nGchlcAXXkDBrYFlC7b3bXA24xr",
		'client first message'
	);
 
	my $server-first = 'r=cT4Z0nGchlcAXXkDBrYFlC7b3bXA24xrB3rw8xNSLYx23V0qdkD/t7ZjoUcyDrTy,s=c2FsdA==,i=4096';
	is(
		$client.final-message($server-first),
		'c=biws,r=cT4Z0nGchlcAXXkDBrYFlC7b3bXA24xrB3rw8xNSLYx23V0qdkD/t7ZjoUcyDrTy,p=lfZL47BCT5wdBisDystprtNLsbA=',
		'client final message'
	);
 
	ok( $client.validate('v=etGS4QFClYMJTMeRBMs0lnWRmV8='), 'server message validated' );
 
}
 
subtest 'Minimum iteration count' => sub {
	{
		# force client nonce to match RFC5802 example
		my $client = get-client( :$client-nonce);
		my $first = $client.first-message();
 
		# RFC5802 example server-first-message, with too low iteration count
		my $server-first = 'r=fyko+d2lbbFgONRv9qkxdawL3rfcNHYJY1ZVvWVs7j,s=QSXCR+Q6sek8bf92,i=4095';
		my $client-final = try $client.final-message($server-first);
		ok not $client-final.defined, 'Too few iterations, returned failure (default max)';
		like $!, rx:s/Too few iterations/, 'Too few iterations, right error (default max)';
	}
 
	{
		# force client nonce to match RFC5802 example
		my $client = get-client( :$client-nonce, :8192minimum-iterations);
		my $first = $client.first-message();
 
		# RFC5802 example server-first-message, with too low iteration count
		my $server-first = 'r=fyko+d2lbbFgONRv9qkxdawL3rfcNHYJY1ZVvWVs7j,s=QSXCR+Q6sek8bf92,i=8191';
		my $client-final = try $client.final-message($server-first);
		ok not $client-final.defined, 'Too few iterations, returned failure (custom iteration count)';
		like $!, rx:s/Too few iterations/, 'Too few iterations (custom iteration count)';
	}
}

subtest 'bad server first message' => sub {
	my $missing-fields = rx:s/Missing fields/;
	my $incomplete-nonce = rx:s/Incomplete nonce/;
	my $too-few-iters = rx:s/Too few iterations/;
	my %bad-messages = (
		''                                              => $missing-fields,
		",r={$client-nonce-encoded}abc,i=99"            => $missing-fields,
		"r={$client-nonce-encoded}abc,s=dlkfakdf"       => $missing-fields,
		"r={$client-nonce-encoded}abc,s=dlkfakdf,i="    => $missing-fields,
		'r=sadkasdllk,s=akdjad,i=99'                    => $incomplete-nonce,
		"r=$client-nonce-encoded,s=akdjad,i=99"         => $incomplete-nonce,
		"r={$client-nonce-encoded}abc,s=def,i=-1000"    => $too-few-iters,
		"r={$client-nonce-encoded}abc,s=def,i=-1.00"    => $too-few-iters,
		"r={$client-nonce-encoded}abc,s=def,i=afdkj"    => $too-few-iters,
	);
 
	for %bad-messages.sort -> (:key($bad), :value($expected)) {
		my $client = try get-client(:$client-nonce);
		nok(try { $client.final-message($bad) }, "parse error for <$bad>");
		like($!, $expected, "parse error for <$bad> matches {$expected.raku}");
	}
}
 
subtest 'unsupported features' => sub {
	my $client = get-client;
	ok (
		not try { $client.final-message('m=1234,r=adlskjas,s=ldkjfalfdj,i=1000') },
		'mandatory extension receiving server-first-message',
	);
 
} 

done-testing;


