package Net::Hawk::Server { use v6; use Net::Hawk::Utils; use Net::Hawk::Crypto; use URI; use URI::Escape; use MIME::Base64; our sub authenticate( %req!, &credentials_func:($,&)!, %options!, &callback:($,%,%)!, ) { %options //= sub ($,$,&nonceCallback) { return &nonceCallback.() }; %options //= 60; my $now = now_msecs(%options//0); my %request = parse_request(%req,%options); my $attributes = try { parse_authorization_header(%request); }; warn $attributes.perl; return &callback.($!,{},{}) unless $attributes; my %artifacts = ( %request :p, resource => %request, $attributes :p, ); if not $attributes{all()} :exists { return &callback.( Net::Hawk::Errors::BadRequest.new( text => 'Missing attributes', value => %request, ), Nil, %artifacts, ); }; &credentials_func.( $attributes, sub ($err,%credentials) { if $err { return &callback.($err,%credentials,%artifacts); }; if not %credentials { return &callback.( Net::Hawk::Errors::UnAuthorized.new( text => 'Unknown credentials', ), Nil, %artifacts, ); }; if not %credentials{all()}.defined { return &callback.( Net::Hawk::Errors::Internal.new( text => 'Invalid credentials', ), %credentials, %artifacts, ); }; if not is_valid_hash_algorithm %credentials { return &callback.( Net::Hawk::Errors::Internal.new( text => 'Unknown algorithm', ), %credentials, %artifacts, ); }; my $mac = calculate_mac('header',%credentials,%artifacts); unless $mac eq $attributes { # DANGER! this should be a fixed-time comparison! return &callback.( Net::Hawk::Errors::UnAuthorized.new( text => 'Bad mac', ), %credentials, %artifacts, ); }; if (%options.defined) { if not $attributes { return &callback.( Net::Hawk::Errors::UnAuthorized.new( text => 'Missing required payload hash', ), %credentials, %artifacts, ); }; my $hash = calculate_payload_hash( %options, %credentials, %request, ); unless $hash eq $attributes { # DANGER! this should be a fixed-time comparison! return &callback.( Net::Hawk::Errors::UnAuthorized.new( text => 'Bad payload hash', ), %credentials, %artifacts, ); }; }; %options.( $attributes, $attributes, sub ($err) { if $err { return &callback.( Net::Hawk::Errors::UnAuthorized.new( text => 'Invalid nonce', ), %credentials, %artifacts, ); }; if abs(($attributes * 1000) - $now) > (%options * 1000) { my $tsm = timestamp_message( %credentials, %options, ); return &callback.( Net::Hawk::Errors::UnAuthorized.new( text => 'Stale timestamp', tsm => $tsm, ), %credentials, %artifacts, ); }; return &callback.(Nil,%credentials,%artifacts); }, ); }, ); }; our sub authenticateBewit( %req, &credentials_func:($,&)!, %options!, &callback:($,%,%)!, ) { my $now = now_msecs(%options//0); my %request = parse_request(%req,%options); my $resource = URI.new(%request); return &callback.(Net::Hawk::Errors::UnAuthorized.new,{},{}) unless $resource; my $bewit_param = $resource.query_form; return &callback.( Net::Hawk::Errors::UnAuthorized.new( text => 'Empty bewit', ), {}, {}, ) unless $bewit_param; return &callback.( Net::Hawk::Errors::UnAuthorized.new( text => 'Invalid method', ), {}, {}, ) unless %request eq any(); return &callback.( Net::Hawk::Errors::BadRequest.new( text => 'Multiple authentications', ), {}, {}, ) if %request; # we should throw if bad b64 encoding… my $bewit_str = MIME::Base64.new.decode-str($bewit_param); my @bewit_parts = $bewit_str.split('\\'); return &callback.( Net::Hawk::Errors::BadRequest.new( text => 'Invalid bewit structure', value => $bewit_str, ), {}, {}, ) unless @bewit_parts == 4; my %bewit = ( id => @bewit_parts[0], exp => try { :10(@bewit_parts[1]) }, mac => @bewit_parts[2], ext => @bewit_parts[3] // '', ); return &callback.( Net::Hawk::Errors::BadRequest.new( text => 'Missing bewit attributes', ), {}, {}, ) unless %bewit{all }.defined; return &callback.( Net::Hawk::Errors::UnAuthorized.new( text => 'Access expired', ), {}, {}, ) if %bewit*1000 <= $now; # the URI object is immutable, and all its attributes are # private so I can't even use 'clone' to get a modified object my $url = %request.subst( /( | '&') bewit\=.*? ( '&'| $ )/, { $1 && $2 ?? '&' !! '' } ); &credentials_func.( %bewit, sub ($err,%credentials) { return &callback.($err,%credentials//{},%bewit//{}) if $err; return &callback.( Net::Hawk::Errors::UnAuthorized.new( text => 'Unknown credentials', ), {}, %bewit, ) unless %credentials; return &callback.( Net::Hawk::Errors::Internal.new( text => 'Invalid credentials', ), %credentials, %bewit, ) unless %credentials{all }.defined; if not is_valid_hash_algorithm %credentials { return &callback.( Net::Hawk::Errors::Internal.new( text => 'Unknown algorithm', ), %credentials, %bewit, ); }; my $mac = calculate_mac('bewit',%credentials,{ ts => %bewit, nonce => '', method => 'GET', resource => $url, %request :p, %bewit :p, }); unless $mac eq %bewit { # DANGER! this should be a fixed-time comparison! return &callback.( Net::Hawk::Errors::UnAuthorized.new( text => 'Bad mac', ), %credentials, %bewit, ); }; return &callback.(Nil,%credentials,%bewit); }, ); }; };