Bod has asked for the wisdom of the Perl Monks concerning the following question:

As part of connecting to LinkedIn using LWP::Authen::OAuth2, I have decided to write a sub-class of LWP::Authen::OAuth2::ServiceProvider which is designed to be sub-classed for exactly this kind of application. This way, it will hopefully be useful to other people.

However, I need to override a method which has not been designed to be overridden! The LinkedIn API doesn't comply with the OAuth2 spec. The token_type parameter is mandatory but missing. Therefore, I am trying to inject it into the response from LinkedIn by sub-classing the method, adding the missing parameter and then calling the method in the super class. But I am not managing to get the injection to work.

Here is the sub in LWP::Authen::OAuth2::ServiceProvider that I am overridding:

# Attempts to construct tokens, returns the access_token (which may ha +ve a # request token embedded). sub construct_tokens { my ($self, $oauth2, $response) = @_; # The information that I need. my $content = eval {$response->decoded_content}; if (not defined($content)) { $content = ''; } my $data = eval {decode_json($content)}; my $parse_error = $@; my $token_endpoint = $self->token_endpoint(); # Can this have done wrong? Let me list the ways... if ($parse_error) { # "Should not happen", hopefully just network. # Tell the programmer everything. my $status = $response->status_line; return <<"EOT" Token endpoint gave invalid JSON in response. Endpoint: $token_endpoint Status: $status Parse error: $parse_error JSON: $content EOT } elsif ($data->{error}) { # Assume a valid OAuth 2 error message. my $message = "OAuth2 error: $data->{error}"; # Do we have a mythical service provider that gives us more? if ($data->{error_uri}) { # They seem to have a web page with detail. $message .= "\n$data->{error_uri} may say more.\n"; } if ($data->{error_description}) { # Wow! Thank you! $message .= "\n\nDescription: $data->{error_description}\n +"; } return $message; } elsif (not $data->{token_type}) { # Someone failed to follow the spec... return <<"EOT"; Token endpoint missing expected token_type in successful response. Endpoint: $token_endpoint JSON: $content EOT } my $type = $self->access_token_class(lc($data->{token_type})); if ($type !~ /^[\w\:]+\z/) { # We got an error. :-( return $type; } eval {load($type)}; if ($@) { # MAKE THIS FATAL. (Clearly Perl code is simply wrong.) confess("Loading $type for $data->{token_type} gave error: $@" +); } # Try to make an access token. my $access_token = $type->from_ref($data); if (not ref($access_token)) { # This should be an error message of some sort. return $access_token; } else { # WE SURVIVED! EVERYTHING IS GOOD! if ($oauth2->access_token) { $access_token->copy_refresh_from($oauth2->access_token); } return $access_token; } }
The author has commented the point it fails as # Someone failed to follow the spec...!

This is my sub that overrides the above...

sub construct_tokens { my ($self, $oauth2, $response) = @_; my $content = eval {$response->decoded_content}; eval {decode_json($content)}; $response->push_header( 'token_type', 'Bearer' ) unless $@; $self->SUPER::construct_tokens($oauth2, $response); }
I'm trying to set token_type as Bearer so that the rest of the sub in the superclass doesn't complain.

Is there a good way to to inject this parameter or am I approaching this in the wrong way?

Replies are listed 'Best First'.
Re: Injecting a value into HTTP::Header
by 1nickt (Canon) on Apr 19, 2021 at 11:34 UTC

    Hi, "not managing to get the injection to work" doesn't really describe the problem well. What is the output? I assume there's an error with push_header() since that does not appear as a documented method on an HTTP::Response object. Try$r-%3Eheader(-$field-=%3E-$value-).

    Hope this helps!

    The way forward always starts with a minimal test.
      Hope this helps!

      It helped immensely thank you...
      It forced me to go and find where I had got push_header() from. In doing that search of source code it twigged that it was not the header that needed token_type but the content. With that realisation, the rest sort of fell into place.

      Here is the working overridden sub in case anyone has a similar problem and comes across this in future:

      sub construct_tokens { my ($self, $oauth2, $response) = @_; my $content = eval {decode_json($response->content)}; unless ($@) { $content->{'token_type'} = 'bearer'; $response->content(encode_json($content)); } $self->SUPER::construct_tokens($oauth2, $response); }

      Can anyone explain what the documentation means when is says to contribute one should submit a git pull request? I would like to contribute my LinkedIn solution when it is complete and tested as I am sure I am not the only person wanting to connect to LinkedIn with Perl.

      Hi, "not managing to get the injection to work" doesn't really describe the problem well

      Sorry - yes, re-reading my question shows it is not very clear.

      What I meant was that injecting the token_type parameter is not working. My sub doesn't throw an error but causes the super class sub to throw an error that token_type is missing.

      push_header() since that does not appear as a documented method on an HTTP::Response object

      HTTP::Response extends HTTP::Message which requires's HTTP::Headers and push_header() is a method of this class.

      Although looking at the link you provided sparked the realisation that token_type is not a header at all. It needs to be added to the content rather than the header. So I think I am going to have to decode the JSON content to a Perl data structure, add the token_type parameter before encoding it back to JSON and passing that to the super class' sub.

      I can see running into propblems with the encoding as there doesn't appear to be a way to reverse the effects of decoded_content

        Never mnind, sry, not enough coffee.

        You misunderstand how the inheritance works. See the doc I linked to and run some minimal tests.

        The way forward always starts with a minimal test.