-
-
Notifications
You must be signed in to change notification settings - Fork 55
(#99) Implemented basic authentication and HttpClient decorator to inject authentication header #110
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
(#99) Implemented basic authentication and HttpClient decorator to inject authentication header #110
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 <a href="https://docs.docker.com/engine/api/v1.35/#section/Authentication">Authentication</a> | ||
* @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(); | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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> T execute( | ||
final HttpUriRequest request, | ||
final ResponseHandler<? extends T> responseHandler | ||
) throws IOException, ClientProtocolException { | ||
throw new UnsupportedOperationException(); | ||
} | ||
|
||
@Override | ||
public <T> T execute( | ||
final HttpUriRequest request, | ||
final ResponseHandler<? extends T> responseHandler, | ||
final HttpContext context | ||
) throws IOException, ClientProtocolException { | ||
throw new UnsupportedOperationException(); | ||
} | ||
|
||
@Override | ||
public <T> 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> T execute( | ||
final HttpHost target, final HttpRequest request, | ||
final ResponseHandler<? extends T> responseHandler, | ||
final HttpContext context | ||
) throws IOException, ClientProtocolException { | ||
throw new UnsupportedOperationException(); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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 <a href="https://docs.docker.com/engine/api/v1.35/#section/Authentication">Authentication</a> | ||
* @since 0.0.1 | ||
*/ | ||
public final class Credentials implements Auth { | ||
/** | ||
* The base64-encoded JSON structure holding the credentials. | ||
*/ | ||
private final Supplier<String> encoded; | ||
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @llorllale Why did you make this a There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @amihaiemil just wanted to coalesce all those parameters into one single instance attribute There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. @llorllale I see, ok |
||
|
||
/** | ||
* 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(); | ||
} | ||
} |
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -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) | ||
); | ||
} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@llorllale Why do we need the
Token
implementation and how would it be used?Uh oh!
There was an error while loading. Please reload this page.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
@amihaiemil the token would be for the identity token alternative described here