diff --git a/src/main/java/com/amihaiemil/docker/Auth.java b/src/main/java/com/amihaiemil/docker/Auth.java
new file mode 100644
index 00000000..db24150a
--- /dev/null
+++ b/src/main/java/com/amihaiemil/docker/Auth.java
@@ -0,0 +1,44 @@
+/**
+ * Copyright (c) 2018, Mihai Emil Andronache
+ * All rights reserved.
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * 1)Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2)Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3)Neither the name of docker-java-api nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.amihaiemil.docker;
+
+/**
+ * Authentication for Docker API.
+ * @author George Aristy (george.aristy@gmail.com)
+ * @version $Id$
+ * @see Authentication
+ * @since 0.0.1
+ * @todo #99:30min Implement a new auth named 'Token' that will hold the user's
+ * identity token from the auth endpoint. Implement some operation that would
+ * call the /auth endpoint and obtain a token.
+ */
+public interface Auth {
+ /**
+ * This {@link Auth} as a Base-64 encoded string.
+ * @return This auth as a base64-encoded string.
+ */
+ String encoded();
+}
diff --git a/src/main/java/com/amihaiemil/docker/AuthHttpClient.java b/src/main/java/com/amihaiemil/docker/AuthHttpClient.java
new file mode 100644
index 00000000..299c6b56
--- /dev/null
+++ b/src/main/java/com/amihaiemil/docker/AuthHttpClient.java
@@ -0,0 +1,144 @@
+/**
+ * Copyright (c) 2018, Mihai Emil Andronache
+ * All rights reserved.
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * 1)Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2)Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3)Neither the name of docker-java-api nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.amihaiemil.docker;
+
+import java.io.IOException;
+import org.apache.http.HttpHost;
+import org.apache.http.HttpRequest;
+import org.apache.http.HttpResponse;
+import org.apache.http.client.ClientProtocolException;
+import org.apache.http.client.HttpClient;
+import org.apache.http.client.ResponseHandler;
+import org.apache.http.client.methods.HttpUriRequest;
+import org.apache.http.conn.ClientConnectionManager;
+import org.apache.http.params.HttpParams;
+import org.apache.http.protocol.HttpContext;
+
+/**
+ * An authenticated HttpClient.
+ * @author George Aristy (george.aristy@gmail.com)
+ * @version $Id$
+ * @since 0.0.1
+ * @todo #99:30min Start decorating our internal HttpClients with this one
+ * for calls that require authentication headers.
+ */
+final class AuthHttpClient implements HttpClient {
+ /**
+ * The decorated HttpClient.
+ */
+ private final HttpClient origin;
+ /**
+ * The authentication to inject into requests.
+ */
+ private final Auth authentication;
+
+ /**
+ * Ctor.
+ * @param origin The HttpClient to decorate.
+ * @param authentication The authentication to inject into requests.
+ */
+ AuthHttpClient(final HttpClient origin, final Auth authentication) {
+ this.origin = origin;
+ this.authentication = authentication;
+ }
+
+ @Override
+ public HttpParams getParams() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public ClientConnectionManager getConnectionManager() {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public HttpResponse execute(final HttpUriRequest request)
+ throws IOException, ClientProtocolException {
+ final String header = "X-Registry-Auth";
+ if (!request.containsHeader(header)) {
+ request.setHeader(header, this.authentication.encoded());
+ }
+ return this.origin.execute(request);
+ }
+
+ @Override
+ public HttpResponse execute(
+ final HttpUriRequest request, final HttpContext context
+ ) throws IOException, ClientProtocolException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public HttpResponse execute(
+ final HttpHost target, final HttpRequest request
+ ) throws IOException, ClientProtocolException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public HttpResponse execute(
+ final HttpHost target, final HttpRequest request,
+ final HttpContext context
+ ) throws IOException, ClientProtocolException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public T execute(
+ final HttpUriRequest request,
+ final ResponseHandler extends T> responseHandler
+ ) throws IOException, ClientProtocolException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public T execute(
+ final HttpUriRequest request,
+ final ResponseHandler extends T> responseHandler,
+ final HttpContext context
+ ) throws IOException, ClientProtocolException {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public T execute(
+ final HttpHost target, final HttpRequest request,
+ final ResponseHandler extends T> responseHandler
+ ) throws IOException, ClientProtocolException {
+ throw new UnsupportedOperationException();
+ }
+
+ // @checkstyle ParameterNumber (5 lines)
+ @Override
+ public T execute(
+ final HttpHost target, final HttpRequest request,
+ final ResponseHandler extends T> responseHandler,
+ final HttpContext context
+ ) throws IOException, ClientProtocolException {
+ throw new UnsupportedOperationException();
+ }
+}
diff --git a/src/main/java/com/amihaiemil/docker/Credentials.java b/src/main/java/com/amihaiemil/docker/Credentials.java
new file mode 100644
index 00000000..37f8cb69
--- /dev/null
+++ b/src/main/java/com/amihaiemil/docker/Credentials.java
@@ -0,0 +1,73 @@
+/**
+ * Copyright (c) 2018, Mihai Emil Andronache
+ * All rights reserved.
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * 1)Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2)Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3)Neither the name of docker-java-api nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.amihaiemil.docker;
+
+import java.nio.charset.StandardCharsets;
+import java.util.Base64;
+import java.util.function.Supplier;
+import javax.json.Json;
+
+/**
+ * An {@link Auth} supporting bare user credentials.
+ * @author George Aristy (george.aristy@gmail.com)
+ * @version $Id$
+ * @see Authentication
+ * @since 0.0.1
+ */
+public final class Credentials implements Auth {
+ /**
+ * The base64-encoded JSON structure holding the credentials.
+ */
+ private final Supplier encoded;
+
+ /**
+ * Ctor.
+ * @param user The username.
+ * @param pwd The user's password.
+ * @param email The user's email address.
+ * @param server Domain/IP without a protocol.
+ * @checkstyle ParameterNumber (4 lines)
+ */
+ public Credentials(
+ final String user, final String pwd,
+ final String email, final String server
+ ) {
+ this.encoded = () -> Base64.getEncoder().encodeToString(
+ Json.createObjectBuilder()
+ .add("username", user)
+ .add("password", pwd)
+ .add("email", email)
+ .add("serveraddress", server)
+ .build().toString()
+ .getBytes(StandardCharsets.UTF_8)
+ );
+ }
+
+ @Override
+ public String encoded() {
+ return this.encoded.get();
+ }
+}
diff --git a/src/main/java/com/amihaiemil/docker/RtImages.java b/src/main/java/com/amihaiemil/docker/RtImages.java
index 7aeefe50..41144dda 100644
--- a/src/main/java/com/amihaiemil/docker/RtImages.java
+++ b/src/main/java/com/amihaiemil/docker/RtImages.java
@@ -93,11 +93,6 @@ public Iterable iterate() throws IOException {
}
}
- // @todo #83:30min Several API calls required an authentication header as
- // explained here:
- // https://docs.docker.com/engine/api/v1.35/#section/Authentication
- // (including Images.create()). Find a way to make a reusable object from
- // that action and introduce it here.
// @checkstyle ParameterNumber (4 lines)
@Override
public Images create(
diff --git a/src/test/java/com/amihaiemil/docker/AuthHttpClientTestCase.java b/src/test/java/com/amihaiemil/docker/AuthHttpClientTestCase.java
new file mode 100644
index 00000000..c4fbd2f2
--- /dev/null
+++ b/src/test/java/com/amihaiemil/docker/AuthHttpClientTestCase.java
@@ -0,0 +1,92 @@
+/**
+ * Copyright (c) 2018, Mihai Emil Andronache
+ * All rights reserved.
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * 1)Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2)Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3)Neither the name of docker-java-api nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.amihaiemil.docker;
+
+import org.apache.http.Header;
+import org.apache.http.client.HttpClient;
+import org.apache.http.client.methods.HttpGet;
+import org.apache.http.client.methods.HttpUriRequest;
+import org.apache.http.message.BasicHeader;
+import org.hamcrest.MatcherAssert;
+import org.hamcrest.Matchers;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.mockito.Mockito;
+
+/**
+ * Unit tests for {@link AuthHttpClient}.
+ * @author George Aristy (george.aristy@gmail.com)
+ * @version $Id$
+ * @since 0.0.1
+ */
+public final class AuthHttpClientTestCase {
+ /**
+ * Mock HttpClient that does nothing.
+ */
+ private static HttpClient noOpClient;
+
+ /**
+ * Setup the mock http client.
+ * @throws Exception If something does wrong.
+ */
+ @BeforeClass
+ public static void setup() throws Exception {
+ noOpClient = Mockito.mock(HttpClient.class);
+ Mockito.when(noOpClient.execute(Mockito.any(HttpUriRequest.class)))
+ .thenReturn(null);
+ }
+
+ /**
+ * Must inject the X-Registry-Auth header if absent and set it to the
+ * auth's value.
+ * @throws Exception If something goes wrong.
+ */
+ @Test
+ public void injectsHeaderIfAbsent() throws Exception {
+ final HttpUriRequest request = new HttpGet();
+ new AuthHttpClient(noOpClient, () -> "123").execute(request);
+ MatcherAssert.assertThat(
+ request.getFirstHeader("X-Registry-Auth").getValue(),
+ Matchers.is("123")
+ );
+ }
+
+ /**
+ * Leaves the request's header instact if it exists.
+ * @throws Exception If something goes wrong.
+ */
+ @Test
+ public void leavesExistingHeaderAlone() throws Exception {
+ final Header auth = new BasicHeader("X-Registry-Auth", "12356");
+ final HttpUriRequest request = new HttpGet();
+ request.setHeader(auth);
+ new AuthHttpClient(noOpClient, () -> "abc").execute(request);
+ MatcherAssert.assertThat(
+ request.getFirstHeader("X-Registry-Auth"),
+ Matchers.is(auth)
+ );
+ }
+}
diff --git a/src/test/java/com/amihaiemil/docker/CredentialsTestCase.java b/src/test/java/com/amihaiemil/docker/CredentialsTestCase.java
new file mode 100644
index 00000000..ee919a8a
--- /dev/null
+++ b/src/test/java/com/amihaiemil/docker/CredentialsTestCase.java
@@ -0,0 +1,64 @@
+/**
+ * Copyright (c) 2018, Mihai Emil Andronache
+ * All rights reserved.
+ * Redistribution and use in source and binary forms, with or without
+ * modification, are permitted provided that the following conditions are met:
+ * 1)Redistributions of source code must retain the above copyright notice,
+ * this list of conditions and the following disclaimer.
+ * 2)Redistributions in binary form must reproduce the above copyright notice,
+ * this list of conditions and the following disclaimer in the documentation
+ * and/or other materials provided with the distribution.
+ * 3)Neither the name of docker-java-api nor the names of its
+ * contributors may be used to endorse or promote products derived from
+ * this software without specific prior written permission.
+ * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
+ * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
+ * IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE
+ * ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE
+ * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
+ * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
+ * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
+ * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
+ * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
+ * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
+ * POSSIBILITY OF SUCH DAMAGE.
+ */
+package com.amihaiemil.docker;
+
+import java.nio.charset.StandardCharsets;
+import java.util.Base64;
+import javax.json.Json;
+import org.hamcrest.MatcherAssert;
+import org.hamcrest.Matchers;
+import org.junit.Test;
+
+/**
+ * Unit tests for {@link Credentials}.
+ * @author George Aristy (george.aristy@gmail.com)
+ * @version $Id$
+ * @since 0.0.1
+ */
+public final class CredentialsTestCase {
+ /**
+ * Correctly encodes to base64 all attributes as a JSON object.
+ */
+ @Test
+ public void correctEncoding() {
+ MatcherAssert.assertThat(
+ new Credentials(
+ "user", "pass", "john@doe.com", "server"
+ ).encoded(),
+ Matchers.is(
+ Base64.getEncoder().encodeToString(
+ Json.createObjectBuilder()
+ .add("username", "user")
+ .add("password", "pass")
+ .add("email", "john@doe.com")
+ .add("serveraddress", "server")
+ .build().toString()
+ .getBytes(StandardCharsets.UTF_8)
+ )
+ )
+ );
+ }
+}