API-Docker

 view release on metacpan or  search on metacpan

lib/API/Docker/Role/RegistryAuth.pm  view on Meta::CPAN

  unless ($auth =~ /^\s*\{/) {
    my $b64 = $auth;
    $b64 =~ tr{-_}{+/};
    # decode_base64 tolerates missing padding, so a value that lost its '='
    # somewhere still decodes here; the header side is where the pad matters.
    $json = decode_base64($b64);
  }

  my $config = eval { decode_json($json) };
  croak __PACKAGE__ . '->_registry_auth_config could not read auth as an '
    . 'AuthConfig: ' . $@ unless ref $config eq 'HASH';
  return $config;
}


1;

__END__

=pod

=encoding UTF-8

=head1 NAME

API::Docker::Role::RegistryAuth - AuthConfig encoding shared by the registry-facing endpoints

=head1 VERSION

version 0.004

=head1 SYNOPSIS

    package API::Docker::API::Whatever;
    use Moo;
    with 'API::Docker::Role::RegistryAuth';

    # The header form: X-Registry-Auth on a registry-facing request
    my $header = $self->_registry_auth_header($opts{auth});

    # The map-header form: X-Registry-Config on /build, hostname -> AuthConfig
    my $cfg_header = $self->_registry_config_header($opts{registry_config});

    # The body form: the same credentials as a plain HashRef
    my $config = $self->_registry_auth_config($opts{auth});

=head1 DESCRIPTION

One AuthConfig, three carriers. The Docker Engine takes registry credentials
as a JSON object with the keys C<username>, C<password>, C<serveraddress>,
C<identitytoken> and C<email>, and moves it around in three shapes:

=over

=item * base64url-encoded in the C<X-Registry-Auth> request header, for
C<< POST /images/{name}/push >>, C<< POST /images/create >>,
C<< GET /distribution/{name}/json >> and the C</plugins> family

=item * as a base64url-encoded B<map> of registry hostname to AuthConfig in
the C<X-Registry-Config> request header, for C<< POST /build >> -- one build
may pull base images from several registries, so it carries a set of
credentials rather than one

=item * as the plain JSON request body of C<< POST /auth >>

=back

This role carries the conversion in both directions so every class that
speaks to a registry agrees on it, and so a caller can hand the same C<auth>
argument to any of them.

B<It carries the encoding, not the policy.> Whether a header is sent at all
differs per endpoint on purpose and stays with the endpoint:
L<API::Docker::API::Images/push> sends C<X-Registry-Auth> on B<every> push
because the engine rejects an image push without it, while an anonymous
plugin or distribution call sends B<no> header -- their routers decode the
header and discard the error, so an absent one is the anonymous case rather
than a failure.

=head2 The padding is not optional

The engine decodes C<X-Registry-Auth> with Go's C<base64.URLEncoding>, not
C<RawURLEncoding>, so the C<=> padding is required. Stripping it makes every
push fail with
C<< failed to parse "X-Registry-Auth" header ... unexpected EOF >> -- the
anonymous case included, where the payload C<{}> encodes to C<e30=>: three
characters and one pad.

=head1 METHODS

These are private and composed into the resource classes; they are documented
here because the shapes are one decision, not several.

C<_registry_auth_header($auth)> returns the padded base64url value for
C<X-Registry-Auth>. C<undef> gives the anonymous encoding C<e30=>, a HashRef
is JSON-encoded, and a string that already looks base64-encoded is passed
through -- but respelled into the URL-safe alphabet, so a value pre-encoded in
standard base64 (with C<+> or C</>) reaches the wire as the C<->/C<_> the
engine's C<base64.URLEncoding> decoder expects rather than failing there.

C<_registry_config_header($map)> is the same encoding for C<X-Registry-Config>
on C<< POST /build >>. It takes the same shapes, but the HashRef it JSON-encodes
is a B<map> of registry hostname to AuthConfig
(C<< { 'registry.example:5000' => { username => ..., password => ... } } >>),
not a single AuthConfig.

C<_registry_auth_config($auth)> returns the same credentials as a plain
HashRef for a JSON request body. C<undef> gives C<undef> -- whether that is
an error is the endpoint's call, not this role's. A HashRef is copied, a JSON
object is decoded, and a base64url string is decoded back through both
layers. Anything that does not read as an AuthConfig croaks.

=head1 SEE ALSO

=over

=item * L<API::Docker::API::Images> - C<push>, which always sends the header

=item * L<API::Docker::API::System> - C<auth>, which sends the body form

=item * L<API::Docker::API::Distribution> - registry manifest lookups



( run in 0.919 second using v1.01-cache-2.11-cpan-5c0b1e786e0 )