From b6edbbf983b62e62e0f2fbe5ed02a6ba2cf5dec6 Mon Sep 17 00:00:00 2001 From: Adam Siemion Date: Sat, 23 May 2015 23:42:59 +0200 Subject: [PATCH 01/48] #230 implemented PsTwitter test --- .../takes/facets/auth/social/PsTwitter.java | 115 +++++++++++------- .../facets/auth/social/PsTwitterTest.java | 75 ++++++++++-- 2 files changed, 139 insertions(+), 51 deletions(-) diff --git a/src/main/java/org/takes/facets/auth/social/PsTwitter.java b/src/main/java/org/takes/facets/auth/social/PsTwitter.java index f13f425a4..9d53b5b49 100644 --- a/src/main/java/org/takes/facets/auth/social/PsTwitter.java +++ b/src/main/java/org/takes/facets/auth/social/PsTwitter.java @@ -28,6 +28,7 @@ import com.jcabi.http.response.RestResponse; import java.io.IOException; import java.net.HttpURLConnection; +import java.net.URI; import java.util.Collections; import java.util.Iterator; import java.util.concurrent.ConcurrentHashMap; @@ -54,6 +55,12 @@ @EqualsAndHashCode(of = { "app", "key" }) public final class PsTwitter implements Pass { + /** + * URL for verifying user credentials. + */ + private static final String VERIFY_URL = + "https://api.twitter.com/1.1/account/verify_credentials.json"; + /** * App name. */ @@ -64,27 +71,61 @@ public final class PsTwitter implements Pass { */ private final transient String key; + /** + * Request for fetching app token. + */ + private final transient com.jcabi.http.Request trequest; + + /** + * Request for verifying user credentials. + */ + private final transient com.jcabi.http.Request vcrequest; + /** * Ctor. - * @param gapp Twitter app - * @param gkey Twitter key + * @param tapp Twitter app + * @param tkey Twitter key + */ + public PsTwitter(final String tapp, final String tkey) { + this( + new JdkRequest( + new Href("https://api.twitter.com/oauth2/token") + .with("grant_type", "client_credentials") + .toString() + ), + new JdkRequest(VERIFY_URL), + tapp, + tkey + ); + } + + /** + * Ctor with proper requestor for testing purposes. + * @param ttrequest HTTP request for getting token + * @param tvcrequest HTTP request for verifying credentials + * @param tapp Facebook app + * @param tkey Facebook key + * @checkstyle ParameterNumberCheck (3 lines) */ - public PsTwitter(final String gapp, final String gkey) { - this.app = gapp; - this.key = gkey; + PsTwitter(final com.jcabi.http.Request ttrequest, + final com.jcabi.http.Request tvcrequest, + final String tapp, + final String tkey) { + this.trequest = ttrequest; + this.vcrequest = tvcrequest; + this.app = tapp; + this.key = tkey; } @Override - public Iterator enter(final Request request) - throws IOException { + public Iterator enter(final Request request) throws IOException { return Collections.singleton( - PsTwitter.fetch(this.token()) + this.fetch(this.token()) ).iterator(); } @Override - public Response exit(final Response response, - final Identity identity) { + public Response exit(final Response response, final Identity identity) { return response; } @@ -94,16 +135,27 @@ public Response exit(final Response response, * @return The user found in Twitter * @throws IOException If fails */ - private static Identity fetch(final String token) throws IOException { - final String uri = new Href( - "https://api.twitter.com/1.1/account/verify_credentials.json" - ).with("access_token", token).toString(); - return PsTwitter.parse( - new JdkRequest(uri) - .header("accept", "application/json") - .fetch().as(RestResponse.class) - .assertStatus(HttpURLConnection.HTTP_OK) - .as(JsonResponse.class).json().readObject() + private Identity fetch(final String token) throws IOException { + final JsonObject response = this.vcrequest + .uri() + .set( + URI.create( + new Href(VERIFY_URL) + .with("access_token", token) + .toString() + ) + ) + .back() + .header("accept", "application/json") + .fetch().as(RestResponse.class) + .assertStatus(HttpURLConnection.HTTP_OK) + .as(JsonResponse.class).json().readObject(); + final ConcurrentMap props = + new ConcurrentHashMap(response.size()); + props.put("name", response.getString("name")); + props.put("picture", response.getString("profile_image_url")); + return new Identity.Simple( + String.format("urn:twitter:%d", response.getInt("id")), props ); } @@ -112,12 +164,8 @@ private static Identity fetch(final String token) throws IOException { * @return The token * @throws IOException If failed */ - private String token() - throws IOException { - final String uri = new Href("https://api.twitter.com/oauth2/token") - .with("grant_type", "client_credentials") - .toString(); - return new JdkRequest(uri) + private String token() throws IOException { + return this.trequest .method("POST") .header( "Content-Type", @@ -136,19 +184,4 @@ private String token() .as(JsonResponse.class) .json().readObject().getString("access_token"); } - - /** - * Make identity from JSON object. - * @param json JSON received from Twitter - * @return Identity found - */ - private static Identity parse(final JsonObject json) { - final ConcurrentMap props = - new ConcurrentHashMap(json.size()); - props.put("name", json.getString("name")); - props.put("picture", json.getString("profile_image_url")); - return new Identity.Simple( - String.format("urn:twitter:%d", json.getInt("id")), props - ); - } } diff --git a/src/test/java/org/takes/facets/auth/social/PsTwitterTest.java b/src/test/java/org/takes/facets/auth/social/PsTwitterTest.java index ae854a68b..c94552019 100644 --- a/src/test/java/org/takes/facets/auth/social/PsTwitterTest.java +++ b/src/test/java/org/takes/facets/auth/social/PsTwitterTest.java @@ -24,31 +24,86 @@ package org.takes.facets.auth.social; +import com.jcabi.http.request.FakeRequest; import java.io.IOException; -import org.junit.Ignore; +import java.util.Collections; +import java.util.Map; +import javax.json.Json; +import org.apache.commons.lang.RandomStringUtils; +import org.apache.commons.lang.math.RandomUtils; +import org.hamcrest.CoreMatchers; +import org.hamcrest.MatcherAssert; import org.junit.Test; +import org.takes.facets.auth.Identity; +import org.takes.facets.auth.Pass; +import org.takes.rq.RqFake; /** * Test case for {@link PsTwitter}. * @author Prasath Premkumar (popprem@gmail.com) * @version $Id$ * @since 1.0 + * @checkstyle MagicNumberCheck (500 lines) + * @checkstyle MultipleStringLiteralsCheck (500 lines) */ public final class PsTwitterTest { /** * Twitter authorization process. * @throws IOException If error occurs in the process - * @todo #11:30min/DEV Test to be implemented for PsTwitter - * using a oauth mock library (eg:wiremock). Need to modify - * PsTwitter to accept url from configurations, so that url - * can be changed for test and real env accordingly. - * Response to be stubbed for both token and verify_credentials - * calls and assertions to be performed for the values returned. */ - @Ignore @Test - public void authorizes() throws IOException { - throw new UnsupportedOperationException("not implemented yet"); + public void canLogin() throws IOException { + final int tid = RandomUtils.nextInt(1000); + final String name = this.randomAlphanumeric(); + final String picture = this.randomAlphanumeric(); + final Pass pass = new PsTwitter( + new FakeRequest( + 200, + "HTTP OK", + Collections.>emptyList(), + String.format( + "{\"token_type\":\"bearer\",\"access_token\":\"%s\"}", + this.randomAlphanumeric() + ).getBytes() + ), + new FakeRequest( + 200, + "HTTP OK", + Collections.>emptyList(), + Json.createObjectBuilder() + .add("id", tid) + .add("name", name) + .add("profile_image_url", picture) + .build() + .toString() + .getBytes() + ), + this.randomAlphanumeric(), + this.randomAlphanumeric() + ); + final Identity identity = pass.enter( + new RqFake("GET", "") + ).next(); + MatcherAssert.assertThat( + identity.urn(), + CoreMatchers.equalTo(String.format("urn:twitter:%d", tid)) + ); + MatcherAssert.assertThat( + identity.properties().get("name"), + CoreMatchers.equalTo(name) + ); + MatcherAssert.assertThat( + identity.properties().get("picture"), + CoreMatchers.equalTo(picture) + ); + } + + /** + * Return a random alphanumeric string. + * @return Random alphanumeric string + */ + private String randomAlphanumeric() { + return RandomStringUtils.randomAlphanumeric(10); } } From 7478971d0b1d956604d1684edb103886e08b3f92 Mon Sep 17 00:00:00 2001 From: Endrigo Antonini Date: Mon, 1 Jun 2015 01:01:59 -0300 Subject: [PATCH 02/48] Implemented a take action to check CORS parameters. --- src/main/java/org/takes/tk/TkCORS.java | 118 ++++++++++++++++++ src/test/java/org/takes/tk/TkCORSTest.java | 132 +++++++++++++++++++++ 2 files changed, 250 insertions(+) create mode 100644 src/main/java/org/takes/tk/TkCORS.java create mode 100644 src/test/java/org/takes/tk/TkCORSTest.java diff --git a/src/main/java/org/takes/tk/TkCORS.java b/src/main/java/org/takes/tk/TkCORS.java new file mode 100644 index 000000000..6f72c7b4e --- /dev/null +++ b/src/main/java/org/takes/tk/TkCORS.java @@ -0,0 +1,118 @@ +/** + * The MIT License (MIT) + * + * Copyright (c) 2015 Yegor Bugayenko + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package org.takes.tk; + +import java.io.IOException; +import java.net.HttpURLConnection; +import java.util.HashSet; +import java.util.Set; +import lombok.EqualsAndHashCode; +import org.takes.Request; +import org.takes.Response; +import org.takes.Take; +import org.takes.rs.RsWithHeaders; +import org.takes.rs.RsWithStatus; + +/** + * CORS take. + * + *

This take checks if the request (Origin) is allowed to perform + * the desired action against the list of the given domains.

+ * + *

The specification of CORS can be found on the W3C web site on the + * following link or even on the RFC-6454 specification. + * + * @author Endrigo Antonini (teamed@endrigo.com.br) + * @version $Id$ + * @since 0.20 + */ +@EqualsAndHashCode(of = { "origin" , "alloweddomains" }) +public class TkCORS implements Take { + + /** + * Prefix of HTTP head Origin. + */ + private static final String ORIGIN_PREFIX = "Origin:"; + + /** + * Original take. + */ + private final transient Take origin; + + /** + * List of allowed domains. + */ + private final transient Set alloweddomains; + + /** + * Ctor. + * @param take Original + * @param domains Allow domains + */ + public TkCORS(final Take take, final String... domains) { + this.origin = take; + this.alloweddomains = new HashSet(); + if (domains != null) { + for (final String domain : domains) { + this.alloweddomains.add(domain); + } + } + } + + @Override + public final Response act(final Request req) throws IOException { + boolean hasorigin = false; + Response response; + String domain = ""; + for (final String head : req.head()) { + if (head.startsWith(ORIGIN_PREFIX)) { + hasorigin = true; + domain = head.split(ORIGIN_PREFIX)[1].trim(); + break; + } + } + if (hasorigin && ( + this.alloweddomains.size() == 0 + || this.alloweddomains.contains(domain))) { + response = this.origin.act(req); + final Set headers = new HashSet(); + headers.add("Access-Control-Allow-Credentials: true"); + // @checkstyle LineLengthCheck (1 line) + headers.add("Access-Control-Allow-Methods: OPTIONS, GET, PUT, POST, DELETE, HEAD"); + headers.add( + String.format("Access-Control-Allow-Origin: %s", domain) + ); + response = new RsWithHeaders(response, headers); + } else if (hasorigin) { + final Set headers = new HashSet(); + headers.add("Access-Control-Allow-Credentials: false"); + response = new RsWithStatus(HttpURLConnection.HTTP_FORBIDDEN); + response = new RsWithHeaders(response, headers); + } else { + response = this.origin.act(req); + } + return response; + } +} diff --git a/src/test/java/org/takes/tk/TkCORSTest.java b/src/test/java/org/takes/tk/TkCORSTest.java new file mode 100644 index 000000000..ff82fdfb3 --- /dev/null +++ b/src/test/java/org/takes/tk/TkCORSTest.java @@ -0,0 +1,132 @@ +/** + * The MIT License (MIT) + * + * Copyright (c) 2015 Yegor Bugayenko + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package org.takes.tk; + +import java.io.IOException; +import java.util.HashSet; +import java.util.Set; +import org.hamcrest.MatcherAssert; +import org.hamcrest.Matchers; +import org.junit.Ignore; +import org.junit.Test; +import org.mockito.Mockito; +import org.takes.Request; +import org.takes.Take; +import org.takes.rq.RqFake; +import org.takes.rq.RqWithHeaders; +import org.takes.rs.RsPrint; +import org.takes.rs.RsText; + +/** + * Test case for {@link TkCORS}. + * @author Endrigo Antonini (teamed@endrigo.com.br) + * @version $Id$ + * @since 0.20 + */ +public class TkCORSTest { + + /** + * Execute TkCORS don't sending any origin on the request. + * @throws IOException If some problem inside + */ + @Test + public final void withoutDomain() throws IOException { + final Take take = Mockito.mock(Take.class); + Mockito.doReturn(new RsText()).when(take) + .act(Mockito.any(Request.class)); + final Take cors = new TkCORS(take); + final RsPrint response = new RsPrint(cors.act(new RqFake())); + final String head = response.printHead(); + this.httpStatusOk(head); + } + + /** + * Execute TkCORS sending a origin that is on the allowed domains to + * perform the action. + * @throws IOException If some problem inside + */ + @Test + public final void withCorrectDomain() throws IOException { + final Take take = Mockito.mock(Take.class); + Mockito.doReturn(new RsText()).when(take) + .act(Mockito.any(Request.class)); + final Take cors = new TkCORS( + take, + "http://teamed.io", + "http://example.com" + ); + final Set headers = new HashSet(); + headers.add("Origin: http://teamed.io"); + final Request req = new RqWithHeaders(new RqFake(), headers); + final RsPrint response = new RsPrint(cors.act(req)); + final String head = response.printHead(); + this.httpStatusOk(head); + } + + /** + * Execute TkCORS sending a origin that is on the allowed domains to + * perform the action. TODO + * @throws IOException If some problem inside + */ + @Test + public final void withWrongDomain() throws IOException { + final Take take = Mockito.mock(Take.class); + Mockito.doReturn(new RsText()).when(take) + .act(Mockito.any(Request.class)); + final Take cors = new TkCORS( + take, + "http://www.teamed.io", + "http://sample.com" + ); + final Set headers = new HashSet(); + headers.add("Origin: http://wrong.teamed.io"); + final Request req = new RqWithHeaders(new RqFake(), headers); + final RsPrint response = new RsPrint(cors.act(req)); + final String head = response.printHead(); + MatcherAssert.assertThat( + "It was expected a 403 HTTP status.", + head, + Matchers.containsString("HTTP/1.1 403") + ); + MatcherAssert.assertThat( + "Wrong value on Access Control Allow Credentias param.", + head, + Matchers.containsString("Access-Control-Allow-Credentials: false") + ); + } + + /** + * Checks if the given head contains text that identify it with HTTP 200 + * status. + * @param head HTTP response head. + */ + @Ignore + private void httpStatusOk(final String head) { + MatcherAssert.assertThat( + "Invalid HTTP status.", + head, + Matchers.containsString("HTTP/1.1 200 OK") + ); + } +} From b91b011ce1a45bc107510f0e4595f868fcb01e9f Mon Sep 17 00:00:00 2001 From: Endrigo Antonini Date: Mon, 1 Jun 2015 02:54:44 -0300 Subject: [PATCH 03/48] Added an implementation of the Basic Authentication system. Issue #221 --- .../java/org/takes/facets/auth/PsBasic.java | 123 ++++++++++++++++ .../org/takes/facets/auth/PsBasicTest.java | 132 ++++++++++++++++++ 2 files changed, 255 insertions(+) create mode 100644 src/main/java/org/takes/facets/auth/PsBasic.java create mode 100644 src/test/java/org/takes/facets/auth/PsBasicTest.java diff --git a/src/main/java/org/takes/facets/auth/PsBasic.java b/src/main/java/org/takes/facets/auth/PsBasic.java new file mode 100644 index 000000000..a82a13c15 --- /dev/null +++ b/src/main/java/org/takes/facets/auth/PsBasic.java @@ -0,0 +1,123 @@ +/** + * The MIT License (MIT) + * + * Copyright (c) 2015 Yegor Bugayenko + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package org.takes.facets.auth; + +import java.io.IOException; +import java.util.Base64; +import java.util.Base64.Decoder; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import lombok.EqualsAndHashCode; +import org.takes.Request; +import org.takes.Response; +import org.takes.misc.Opt; + +/** + * Pass that checks the user according RFC-2617. + * + *

The class is immutable and thread-safe. + * + * @author Endrigo Antonini (teamed@endrigo.com.br) + * @version $Id$ + * @since 0.20 + */ +@EqualsAndHashCode(of = { "entry" }) +public final class PsBasic implements Pass { + + /** + * Authorization response HTTP head. + */ + private static final String AUTH_HEAD = "Authorization: Basic"; + + /** + * Entry to validate user information. + */ + private final transient PsBasic.Entry entry; + + /** + * Ctor. + * @param basic Entry + */ + public PsBasic(final PsBasic.Entry basic) { + this.entry = basic; + } + + @Override + @SuppressWarnings("PMD.AvoidInstantiatingObjectsInLoops") + public Opt enter(final Request request) throws IOException { + String authorization = ""; + String user = ""; + String pass = ""; + boolean found = false; + Opt identity = new Opt.Empty(); + for (final String head : request.head()) { + if (head.startsWith(AUTH_HEAD)) { + authorization = head.split(AUTH_HEAD)[1].trim(); + final Decoder decoder = Base64.getDecoder(); + authorization = new String(decoder.decode(authorization)); + user = authorization.split(":")[0]; + pass = authorization.substring(user.length() + 1); + found = true; + break; + } + } + if (found && this.entry.check(user, pass)) { + final ConcurrentMap props = + new ConcurrentHashMap(0); + identity = new Opt.Single( + new Identity.Simple( + String.format("urn:basic:%s", user), + props + ) + ); + } + return identity; + } + + @Override + public Response exit(final Response response, final Identity identity) + throws IOException { + return response; + } + + /** + * Entry interface that is used to check if the received information is + * valid. + * + * @author Endrigo Antonini (teamed@endrigo.com.br) + * @version $Id$ + * @since 0.20 + */ + public interface Entry { + + /** + * Check if is a valid user. + * @param user User + * @param pwd Password + * @return If valid it return true. + */ + boolean check(String user, String pwd); + } + +} diff --git a/src/test/java/org/takes/facets/auth/PsBasicTest.java b/src/test/java/org/takes/facets/auth/PsBasicTest.java new file mode 100644 index 000000000..5d61516e6 --- /dev/null +++ b/src/test/java/org/takes/facets/auth/PsBasicTest.java @@ -0,0 +1,132 @@ +/** + * The MIT License (MIT) + * + * Copyright (c) 2015 Yegor Bugayenko + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package org.takes.facets.auth; + +import java.util.Base64; +import java.util.Base64.Encoder; +import java.util.HashSet; +import java.util.Set; +import org.apache.commons.lang.RandomStringUtils; +import org.hamcrest.CoreMatchers; +import org.hamcrest.MatcherAssert; +import org.hamcrest.Matchers; +import org.junit.Test; +import org.takes.misc.Opt; +import org.takes.rq.RqFake; +import org.takes.rq.RqWithHeaders; + +/** + * Test of {@link PsBasic}. + * @author Endrigo Antonini (teamed@endrigo.com.br) + * @version $Id$ + * @since 0.20 + */ +public class PsBasicTest { + + /** + * Basic Auth. + */ + private static final String AUTH_BASIC = "Authorization: Basic %s"; + + /** + * Test {@link PsBasic} with valid credential. + * @throws Exception if any error occurs + */ + @Test + public final void testValidCredential() throws Exception { + final String user = "user"; + final Set headers = new HashSet(); + headers.add(this.generateHead(user)); + final RqWithHeaders req = this.generateRequest(headers); + final PsBasic basic = new PsBasic( + new PsBasic.Entry() { + @Override + public boolean check(final String user, final String pwd) { + return true; + } + } + ); + final Opt identity = basic.enter(req); + MatcherAssert.assertThat(identity.has(), Matchers.is(true)); + MatcherAssert.assertThat( + identity.get().urn(), + CoreMatchers.equalTo(String.format("urn:basic:%s", user)) + ); + } + + /** + * Test {@link PsBasic} with invalid credential. + * @throws Exception If some problem inside + */ + @Test + public final void testInvalidCredential() throws Exception { + final Set headers = new HashSet(); + headers.add(this.generateHead("username")); + final RqWithHeaders req = this.generateRequest(headers); + final PsBasic basic = new PsBasic( + new PsBasic.Entry() { + @Override + public boolean check(final String user, final String pwd) { + return false; + } + } + ); + final Opt identity = basic.enter(req); + MatcherAssert.assertThat(identity.has(), Matchers.is(false)); + } + + /** + * Generate the string used on the request that store information about + * authentication. + * @param user Username + * @return Header string. + */ + private String generateHead(final String user) { + final String pass = "password"; + final String auth = String.format("%s:%s", user, pass); + final Encoder encoder = Base64.getEncoder(); + final String encoded = new String(encoder.encode(auth.getBytes())); + return String.format(AUTH_BASIC, encoded); + } + + /** + * Generates a request with all headers. + * @param headers Headers. + * @return An instance of {@link RqWithHeaders}. + */ + private RqWithHeaders generateRequest(final Set headers) { + return new RqWithHeaders( + new RqFake( + "GET", + String.format( + "?code=%s", + // @checkstyle MagicNumberCheck (1 line) + RandomStringUtils.randomAlphanumeric(10) + ) + ), + headers + ); + } + +} From a01c3cac279154c9f5f57d57ca874cc589b93c80 Mon Sep 17 00:00:00 2001 From: Endrigo Antonini Date: Tue, 2 Jun 2015 00:30:00 -0300 Subject: [PATCH 04/48] Changed from Base64 (that was added on Java8) to use DatatypeConverter that is available since Java 6. --- src/main/java/org/takes/facets/auth/PsBasic.java | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/src/main/java/org/takes/facets/auth/PsBasic.java b/src/main/java/org/takes/facets/auth/PsBasic.java index a82a13c15..52904e1b9 100644 --- a/src/main/java/org/takes/facets/auth/PsBasic.java +++ b/src/main/java/org/takes/facets/auth/PsBasic.java @@ -24,10 +24,9 @@ package org.takes.facets.auth; import java.io.IOException; -import java.util.Base64; -import java.util.Base64.Decoder; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; +import javax.xml.bind.DatatypeConverter; import lombok.EqualsAndHashCode; import org.takes.Request; import org.takes.Response; @@ -74,8 +73,9 @@ public Opt enter(final Request request) throws IOException { for (final String head : request.head()) { if (head.startsWith(AUTH_HEAD)) { authorization = head.split(AUTH_HEAD)[1].trim(); - final Decoder decoder = Base64.getDecoder(); - authorization = new String(decoder.decode(authorization)); + authorization = new String( + DatatypeConverter.parseBase64Binary(authorization) + ); user = authorization.split(":")[0]; pass = authorization.substring(user.length() + 1); found = true; @@ -119,5 +119,4 @@ public interface Entry { */ boolean check(String user, String pwd); } - } From cac5d63e1b06b7c9a7b12fffc5dd2b8fc0fdc49f Mon Sep 17 00:00:00 2001 From: Endrigo Antonini Date: Tue, 2 Jun 2015 01:00:44 -0300 Subject: [PATCH 05/48] Renamed variable, changed class to be final instead of each method, use Collections.addAll instead of iterating the list, use instead of size, use Collections.singleton for a single value collection instelad o regular collection, improved method names and javadoc of methods. --- src/main/java/org/takes/tk/TkCORS.java | 25 ++++++++++---------- src/test/java/org/takes/tk/TkCORSTest.java | 27 +++++++++------------- 2 files changed, 24 insertions(+), 28 deletions(-) diff --git a/src/main/java/org/takes/tk/TkCORS.java b/src/main/java/org/takes/tk/TkCORS.java index 6f72c7b4e..084b8b7f4 100644 --- a/src/main/java/org/takes/tk/TkCORS.java +++ b/src/main/java/org/takes/tk/TkCORS.java @@ -25,6 +25,8 @@ import java.io.IOException; import java.net.HttpURLConnection; +import java.util.Arrays; +import java.util.Collections; import java.util.HashSet; import java.util.Set; import lombok.EqualsAndHashCode; @@ -48,8 +50,8 @@ * @version $Id$ * @since 0.20 */ -@EqualsAndHashCode(of = { "origin" , "alloweddomains" }) -public class TkCORS implements Take { +@EqualsAndHashCode(of = { "origin" , "allowed" }) +public final class TkCORS implements Take { /** * Prefix of HTTP head Origin. @@ -64,7 +66,7 @@ public class TkCORS implements Take { /** * List of allowed domains. */ - private final transient Set alloweddomains; + private final transient Set allowed; /** * Ctor. @@ -73,16 +75,14 @@ public class TkCORS implements Take { */ public TkCORS(final Take take, final String... domains) { this.origin = take; - this.alloweddomains = new HashSet(); + this.allowed = new HashSet(); if (domains != null) { - for (final String domain : domains) { - this.alloweddomains.add(domain); - } + this.allowed.addAll(Arrays.asList(domains)); } } @Override - public final Response act(final Request req) throws IOException { + public Response act(final Request req) throws IOException { boolean hasorigin = false; Response response; String domain = ""; @@ -94,8 +94,8 @@ public final Response act(final Request req) throws IOException { } } if (hasorigin && ( - this.alloweddomains.size() == 0 - || this.alloweddomains.contains(domain))) { + this.allowed.isEmpty() + || this.allowed.contains(domain))) { response = this.origin.act(req); final Set headers = new HashSet(); headers.add("Access-Control-Allow-Credentials: true"); @@ -106,8 +106,9 @@ public final Response act(final Request req) throws IOException { ); response = new RsWithHeaders(response, headers); } else if (hasorigin) { - final Set headers = new HashSet(); - headers.add("Access-Control-Allow-Credentials: false"); + final Set headers = Collections.singleton( + "Access-Control-Allow-Credentials: false" + ); response = new RsWithStatus(HttpURLConnection.HTTP_FORBIDDEN); response = new RsWithHeaders(response, headers); } else { diff --git a/src/test/java/org/takes/tk/TkCORSTest.java b/src/test/java/org/takes/tk/TkCORSTest.java index ff82fdfb3..187d67b24 100644 --- a/src/test/java/org/takes/tk/TkCORSTest.java +++ b/src/test/java/org/takes/tk/TkCORSTest.java @@ -23,12 +23,10 @@ */ package org.takes.tk; -import java.io.IOException; import java.util.HashSet; import java.util.Set; import org.hamcrest.MatcherAssert; import org.hamcrest.Matchers; -import org.junit.Ignore; import org.junit.Test; import org.mockito.Mockito; import org.takes.Request; @@ -44,14 +42,14 @@ * @version $Id$ * @since 0.20 */ -public class TkCORSTest { +public final class TkCORSTest { /** - * Execute TkCORS don't sending any origin on the request. - * @throws IOException If some problem inside + * TkCORS can handle a connection without origin in the request. + * @throws Exception If some problem inside */ @Test - public final void withoutDomain() throws IOException { + public void withoutOrigin() throws Exception { final Take take = Mockito.mock(Take.class); Mockito.doReturn(new RsText()).when(take) .act(Mockito.any(Request.class)); @@ -62,15 +60,14 @@ public final void withoutDomain() throws IOException { } /** - * Execute TkCORS sending a origin that is on the allowed domains to - * perform the action. - * @throws IOException If some problem inside + * TkCORS can handle connections with correct domain on origin. + * @throws Exception If some problem inside */ @Test - public final void withCorrectDomain() throws IOException { + public void withCorrectDomainOnOrigin() throws Exception { final Take take = Mockito.mock(Take.class); Mockito.doReturn(new RsText()).when(take) - .act(Mockito.any(Request.class)); + .act(Mockito.any(Request.class)); final Take cors = new TkCORS( take, "http://teamed.io", @@ -85,12 +82,11 @@ public final void withCorrectDomain() throws IOException { } /** - * Execute TkCORS sending a origin that is on the allowed domains to - * perform the action. TODO - * @throws IOException If some problem inside + * TkCors can't handle connections with wrong domain on origin. + * @throws Exception If some problem inside */ @Test - public final void withWrongDomain() throws IOException { + public void withWrongDomainOnOrigin() throws Exception { final Take take = Mockito.mock(Take.class); Mockito.doReturn(new RsText()).when(take) .act(Mockito.any(Request.class)); @@ -121,7 +117,6 @@ public final void withWrongDomain() throws IOException { * status. * @param head HTTP response head. */ - @Ignore private void httpStatusOk(final String head) { MatcherAssert.assertThat( "Invalid HTTP status.", From 752aaaa829eafac267d8927e3155e8438ec0ca39 Mon Sep 17 00:00:00 2001 From: Endrigo Antonini Date: Tue, 2 Jun 2015 01:08:16 -0300 Subject: [PATCH 06/48] Replaced to use DatatypeConverter instead Base64 on unit test. --- src/test/java/org/takes/facets/auth/PsBasicTest.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/src/test/java/org/takes/facets/auth/PsBasicTest.java b/src/test/java/org/takes/facets/auth/PsBasicTest.java index 5d61516e6..0f0c35101 100644 --- a/src/test/java/org/takes/facets/auth/PsBasicTest.java +++ b/src/test/java/org/takes/facets/auth/PsBasicTest.java @@ -23,10 +23,9 @@ */ package org.takes.facets.auth; -import java.util.Base64; -import java.util.Base64.Encoder; import java.util.HashSet; import java.util.Set; +import javax.xml.bind.DatatypeConverter; import org.apache.commons.lang.RandomStringUtils; import org.hamcrest.CoreMatchers; import org.hamcrest.MatcherAssert; @@ -105,8 +104,9 @@ public boolean check(final String user, final String pwd) { private String generateHead(final String user) { final String pass = "password"; final String auth = String.format("%s:%s", user, pass); - final Encoder encoder = Base64.getEncoder(); - final String encoded = new String(encoder.encode(auth.getBytes())); + final String encoded = DatatypeConverter.printBase64Binary( + auth.getBytes() + ); return String.format(AUTH_BASIC, encoded); } From 84567d9474c5f74246f41290ffefcd51511cdba7 Mon Sep 17 00:00:00 2001 From: Endrigo Antonini Date: Tue, 2 Jun 2015 02:57:32 -0300 Subject: [PATCH 07/48] Added a fake Take. Improved the re-assign of variables. Made some variables final. Removed the use of Mockito. --- src/main/java/org/takes/tk/TkCORS.java | 13 +++-- src/main/java/org/takes/tk/TkFake.java | 59 ++++++++++++++++++++++ src/test/java/org/takes/tk/TkCORSTest.java | 13 ++--- 3 files changed, 70 insertions(+), 15 deletions(-) create mode 100644 src/main/java/org/takes/tk/TkFake.java diff --git a/src/main/java/org/takes/tk/TkCORS.java b/src/main/java/org/takes/tk/TkCORS.java index 084b8b7f4..ab0a57e27 100644 --- a/src/main/java/org/takes/tk/TkCORS.java +++ b/src/main/java/org/takes/tk/TkCORS.java @@ -84,7 +84,7 @@ public TkCORS(final Take take, final String... domains) { @Override public Response act(final Request req) throws IOException { boolean hasorigin = false; - Response response; + final Response response; String domain = ""; for (final String head : req.head()) { if (head.startsWith(ORIGIN_PREFIX)) { @@ -96,7 +96,6 @@ public Response act(final Request req) throws IOException { if (hasorigin && ( this.allowed.isEmpty() || this.allowed.contains(domain))) { - response = this.origin.act(req); final Set headers = new HashSet(); headers.add("Access-Control-Allow-Credentials: true"); // @checkstyle LineLengthCheck (1 line) @@ -104,13 +103,17 @@ public Response act(final Request req) throws IOException { headers.add( String.format("Access-Control-Allow-Origin: %s", domain) ); - response = new RsWithHeaders(response, headers); + response = new RsWithHeaders(this.origin.act(req), headers); } else if (hasorigin) { final Set headers = Collections.singleton( "Access-Control-Allow-Credentials: false" ); - response = new RsWithStatus(HttpURLConnection.HTTP_FORBIDDEN); - response = new RsWithHeaders(response, headers); + response = new RsWithHeaders( + new RsWithStatus( + HttpURLConnection.HTTP_FORBIDDEN + ), + headers + ); } else { response = this.origin.act(req); } diff --git a/src/main/java/org/takes/tk/TkFake.java b/src/main/java/org/takes/tk/TkFake.java new file mode 100644 index 000000000..6a4a78807 --- /dev/null +++ b/src/main/java/org/takes/tk/TkFake.java @@ -0,0 +1,59 @@ +/** + * The MIT License (MIT) + * + * Copyright (c) 2015 Yegor Bugayenko + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package org.takes.tk; + +import java.io.IOException; +import lombok.EqualsAndHashCode; +import org.takes.Request; +import org.takes.Response; +import org.takes.Take; + +/** + * Fake Take. + * + * @author Endrigo Antonini (teamed@endrigo.com.br) + * @version $Id$ + * @since 0.20 + */ +@EqualsAndHashCode(of = { "response" }) +public final class TkFake implements Take { + + /** + * Response. + */ + private final transient Response response; + + /** + * Ctor. + * @param resp Response + */ + public TkFake(final Response resp) { + this.response = resp; + } + + @Override + public Response act(final Request request) throws IOException { + return this.response; + } +} diff --git a/src/test/java/org/takes/tk/TkCORSTest.java b/src/test/java/org/takes/tk/TkCORSTest.java index 187d67b24..5cd482ee2 100644 --- a/src/test/java/org/takes/tk/TkCORSTest.java +++ b/src/test/java/org/takes/tk/TkCORSTest.java @@ -28,7 +28,6 @@ import org.hamcrest.MatcherAssert; import org.hamcrest.Matchers; import org.junit.Test; -import org.mockito.Mockito; import org.takes.Request; import org.takes.Take; import org.takes.rq.RqFake; @@ -50,9 +49,7 @@ public final class TkCORSTest { */ @Test public void withoutOrigin() throws Exception { - final Take take = Mockito.mock(Take.class); - Mockito.doReturn(new RsText()).when(take) - .act(Mockito.any(Request.class)); + final Take take = new TkFake(new RsText()); final Take cors = new TkCORS(take); final RsPrint response = new RsPrint(cors.act(new RqFake())); final String head = response.printHead(); @@ -65,9 +62,7 @@ public void withoutOrigin() throws Exception { */ @Test public void withCorrectDomainOnOrigin() throws Exception { - final Take take = Mockito.mock(Take.class); - Mockito.doReturn(new RsText()).when(take) - .act(Mockito.any(Request.class)); + final Take take = new TkFake(new RsText()); final Take cors = new TkCORS( take, "http://teamed.io", @@ -87,9 +82,7 @@ public void withCorrectDomainOnOrigin() throws Exception { */ @Test public void withWrongDomainOnOrigin() throws Exception { - final Take take = Mockito.mock(Take.class); - Mockito.doReturn(new RsText()).when(take) - .act(Mockito.any(Request.class)); + final Take take = new TkFake(new RsText()); final Take cors = new TkCORS( take, "http://www.teamed.io", From 6292fc3b13f68765206f4a07bc9c74a2f124dcf6 Mon Sep 17 00:00:00 2001 From: Endrigo Antonini Date: Tue, 2 Jun 2015 23:25:44 -0300 Subject: [PATCH 08/48] Improvements that was requested on the PR. --- .../java/org/takes/facets/auth/PsBasic.java | 93 ++++++++-- .../org/takes/facets/auth/PsBasicTest.java | 163 +++++++++++++----- 2 files changed, 199 insertions(+), 57 deletions(-) diff --git a/src/main/java/org/takes/facets/auth/PsBasic.java b/src/main/java/org/takes/facets/auth/PsBasic.java index 52904e1b9..c7bf85331 100644 --- a/src/main/java/org/takes/facets/auth/PsBasic.java +++ b/src/main/java/org/takes/facets/auth/PsBasic.java @@ -65,29 +65,23 @@ public PsBasic(final PsBasic.Entry basic) { @Override @SuppressWarnings("PMD.AvoidInstantiatingObjectsInLoops") public Opt enter(final Request request) throws IOException { - String authorization = ""; - String user = ""; - String pass = ""; - boolean found = false; + BasicAuth auth = new BasicAuth("", ""); Opt identity = new Opt.Empty(); for (final String head : request.head()) { if (head.startsWith(AUTH_HEAD)) { - authorization = head.split(AUTH_HEAD)[1].trim(); - authorization = new String( - DatatypeConverter.parseBase64Binary(authorization) - ); - user = authorization.split(":")[0]; - pass = authorization.substring(user.length() + 1); - found = true; + auth = this.readAuthContentOnHead(head); break; } } - if (found && this.entry.check(user, pass)) { + if (!auth.isEmpty() && this.entry.check( + auth.getUser(), + auth.getPass() + )) { final ConcurrentMap props = new ConcurrentHashMap(0); identity = new Opt.Single( new Identity.Simple( - String.format("urn:basic:%s", user), + String.format("urn:basic:%s", auth.getUser()), props ) ); @@ -101,6 +95,24 @@ public Response exit(final Response response, final Identity identity) return response; } + /** + * Read authentication content that is received on the head. + * @param head Head + * @return BasicAuth instance. + */ + private BasicAuth readAuthContentOnHead(final String head) { + final String authorization = new String( + DatatypeConverter.parseBase64Binary( + head.split(AUTH_HEAD)[1].trim() + ) + ); + final String user = authorization.split(":")[0]; + return new BasicAuth( + user, + authorization.substring(user.length() + 1) + ); + } + /** * Entry interface that is used to check if the received information is * valid. @@ -119,4 +131,59 @@ public interface Entry { */ boolean check(String user, String pwd); } + + /** + * Used to transfer authentication information. + * + * @author Endrigo Antonini (teamed@endrigo.com.br) + * @version $Id$ + * @since 0.20 + */ + private final class BasicAuth { + + /** + * User. + */ + private final String user; + + /** + * Password. + */ + private final String pass; + + /** + * Ctor. + * @param username User + * @param password Password + */ + public BasicAuth(final String username, final String password) { + super(); + this.user = username; + this.pass = password; + } + + /** + * Return user. + * @return User. + */ + public String getUser() { + return this.user; + } + + /** + * Return Password. + * @return Password. + */ + public String getPass() { + return this.pass; + } + + /** + * Check if the object is empty. + * @return Return true if user and password is empty. + */ + public boolean isEmpty() { + return this.getUser().isEmpty() && this.getPass().isEmpty(); + } + } } diff --git a/src/test/java/org/takes/facets/auth/PsBasicTest.java b/src/test/java/org/takes/facets/auth/PsBasicTest.java index 0f0c35101..7effed5b7 100644 --- a/src/test/java/org/takes/facets/auth/PsBasicTest.java +++ b/src/test/java/org/takes/facets/auth/PsBasicTest.java @@ -23,8 +23,6 @@ */ package org.takes.facets.auth; -import java.util.HashSet; -import java.util.Set; import javax.xml.bind.DatatypeConverter; import org.apache.commons.lang.RandomStringUtils; import org.hamcrest.CoreMatchers; @@ -33,6 +31,7 @@ import org.junit.Test; import org.takes.misc.Opt; import org.takes.rq.RqFake; +import org.takes.rq.RqMethod; import org.takes.rq.RqWithHeaders; /** @@ -41,7 +40,7 @@ * @version $Id$ * @since 0.20 */ -public class PsBasicTest { +public final class PsBasicTest { /** * Basic Auth. @@ -49,84 +48,160 @@ public class PsBasicTest { private static final String AUTH_BASIC = "Authorization: Basic %s"; /** - * Test {@link PsBasic} with valid credential. + * PsBasic can handle connection with valid credential. * @throws Exception if any error occurs */ @Test - public final void testValidCredential() throws Exception { - final String user = "user"; - final Set headers = new HashSet(); - headers.add(this.generateHead(user)); - final RqWithHeaders req = this.generateRequest(headers); - final PsBasic basic = new PsBasic( + public void handleConnectionWithValidCredential() throws Exception { + final String user = "john"; + final String pass = "pass"; + final RqWithHeaders req = new RqWithHeaders( + new RqFake( + RqMethod.GET, + String.format( + "?valid_code=%s", + // @checkstyle MagicNumberCheck (1 line) + RandomStringUtils.randomAlphanumeric(10) + ) + ), + this.generateAuthenticateHead(user, pass) + ); + final Opt identity = new PsBasic( new PsBasic.Entry() { @Override - public boolean check(final String user, final String pwd) { - return true; + public boolean check(final String username, final String pwd) { + return user.equals(username) && pass.equals(pwd); } } - ); - final Opt identity = basic.enter(req); + ).enter(req); MatcherAssert.assertThat(identity.has(), Matchers.is(true)); MatcherAssert.assertThat( identity.get().urn(), - CoreMatchers.equalTo(String.format("urn:basic:%s", user)) + CoreMatchers.equalTo(this.generateIdentityUrn(user)) ); } /** - * Test {@link PsBasic} with invalid credential. + * PsBasic can handle connection with invalid credential. * @throws Exception If some problem inside */ @Test - public final void testInvalidCredential() throws Exception { - final Set headers = new HashSet(); - headers.add(this.generateHead("username")); - final RqWithHeaders req = this.generateRequest(headers); - final PsBasic basic = new PsBasic( + public void handleConnectionWithInvalidCredential() throws Exception { + final RqWithHeaders req = new RqWithHeaders( + new RqFake( + RqMethod.GET, + String.format( + "?invalid_code=%s", + // @checkstyle MagicNumberCheck (1 line) + RandomStringUtils.randomAlphanumeric(10) + ) + ), + this.generateAuthenticateHead("username", "wrong") + ); + final Opt identity = new PsBasic( new PsBasic.Entry() { @Override public boolean check(final String user, final String pwd) { return false; } } + ).enter(req); + MatcherAssert.assertThat(identity.has(), Matchers.is(false)); + } + + /** + * PsBasic can handle multiple headers with valid credential. + * @throws Exception If some problem inside + */ + @Test + public void handleMultipleHeadersWithValidCredential() throws Exception { + final String user = "bill"; + final String pass = "changeit"; + final RqWithHeaders req = new RqWithHeaders( + new RqFake( + RqMethod.GET, + String.format( + "?multiple_code=%s", + // @checkstyle MagicNumberCheck (1 line) + RandomStringUtils.randomAlphanumeric(10) + ) + ), + this.generateAuthenticateHead(user, pass), + "Referer: http://teamed.io/", + "Connection:keep-alive", + "Content-Encoding:gzip", + "X-Check-Cacheable:YES", + "X-Powered-By:Java/1.7" ); - final Opt identity = basic.enter(req); + final Opt identity = new PsBasic( + new PsBasic.Entry() { + @Override + public boolean check(final String username, final String pwd) { + return user.equals(username) && pass.equals(pwd); + } + } + ).enter(req); + MatcherAssert.assertThat(identity.has(), Matchers.is(true)); + MatcherAssert.assertThat( + identity.get().urn(), + CoreMatchers.equalTo(this.generateIdentityUrn(user)) + ); + } + + /** + * PsBasic can handle multiple headers with invalid content. + * @throws Exception If some problem inside + */ + @Test + public void handleMultipleHeadersWithInvalidContent() throws Exception { + final String user = "user"; + final String pass = "password"; + final RqWithHeaders req = new RqWithHeaders( + new RqFake( + "XPTO", + "/wrong-url" + ), + String.format("XYZ%s", this.generateAuthenticateHead(user, pass)), + "XYZReferer: http://teamed.io/", + "XYZConnection:keep-alive", + "XYZContent-Encoding:gzip", + "XYZX-Check-Cacheable:YES", + "XYZX-Powered-By:Java/1.7" + ); + final Opt identity = new PsBasic( + new PsBasic.Entry() { + @Override + public boolean check(final String username, final String pwd) { + return user.equals(username) && pass.equals(pwd); + } + } + ).enter(req); MatcherAssert.assertThat(identity.has(), Matchers.is(false)); } + /** + * Generate the identity urn. + * @param user User + * @return URN + */ + private String generateIdentityUrn(final String user) { + return String.format("urn:basic:%s", user); + } /** * Generate the string used on the request that store information about * authentication. * @param user Username + * @param pass Password * @return Header string. */ - private String generateHead(final String user) { - final String pass = "password"; + private String generateAuthenticateHead( + final String user, + final String pass + ) { final String auth = String.format("%s:%s", user, pass); final String encoded = DatatypeConverter.printBase64Binary( auth.getBytes() ); return String.format(AUTH_BASIC, encoded); } - - /** - * Generates a request with all headers. - * @param headers Headers. - * @return An instance of {@link RqWithHeaders}. - */ - private RqWithHeaders generateRequest(final Set headers) { - return new RqWithHeaders( - new RqFake( - "GET", - String.format( - "?code=%s", - // @checkstyle MagicNumberCheck (1 line) - RandomStringUtils.randomAlphanumeric(10) - ) - ), - headers - ); - } - } From 6d0751db47dc11eec4c3c0ac7f8830350af5d59f Mon Sep 17 00:00:00 2001 From: Endrigo Antonini Date: Wed, 3 Jun 2015 00:05:30 -0300 Subject: [PATCH 09/48] Improvements that was requested on the PR. --- src/main/java/org/takes/Response.java | 6 ++++ src/main/java/org/takes/tk/TkCORS.java | 6 ++-- src/test/java/org/takes/tk/TkCORSTest.java | 35 ++++++++++------------ 3 files changed, 26 insertions(+), 21 deletions(-) diff --git a/src/main/java/org/takes/Response.java b/src/main/java/org/takes/Response.java index 3a18aa631..6ee5e7a6d 100644 --- a/src/main/java/org/takes/Response.java +++ b/src/main/java/org/takes/Response.java @@ -55,6 +55,12 @@ */ public interface Response { + /** + * Http status 200. + *

HTTP/1.1 200 OK

+ */ + String HTTP_STATUS_200 = "HTTP/1.1 200 OK"; + /** * HTTP response head. * @return Lines in HTTP response head diff --git a/src/main/java/org/takes/tk/TkCORS.java b/src/main/java/org/takes/tk/TkCORS.java index ab0a57e27..0740d1fd6 100644 --- a/src/main/java/org/takes/tk/TkCORS.java +++ b/src/main/java/org/takes/tk/TkCORS.java @@ -98,8 +98,10 @@ public Response act(final Request req) throws IOException { || this.allowed.contains(domain))) { final Set headers = new HashSet(); headers.add("Access-Control-Allow-Credentials: true"); - // @checkstyle LineLengthCheck (1 line) - headers.add("Access-Control-Allow-Methods: OPTIONS, GET, PUT, POST, DELETE, HEAD"); + headers.add( + // @checkstyle LineLengthCheck (1 line) + "Access-Control-Allow-Methods: OPTIONS, GET, PUT, POST, DELETE, HEAD" + ); headers.add( String.format("Access-Control-Allow-Origin: %s", domain) ); diff --git a/src/test/java/org/takes/tk/TkCORSTest.java b/src/test/java/org/takes/tk/TkCORSTest.java index 5cd482ee2..9b75dfd39 100644 --- a/src/test/java/org/takes/tk/TkCORSTest.java +++ b/src/test/java/org/takes/tk/TkCORSTest.java @@ -29,6 +29,7 @@ import org.hamcrest.Matchers; import org.junit.Test; import org.takes.Request; +import org.takes.Response; import org.takes.Take; import org.takes.rq.RqFake; import org.takes.rq.RqWithHeaders; @@ -44,16 +45,20 @@ public final class TkCORSTest { /** - * TkCORS can handle a connection without origin in the request. + * TkCORS can handle connections without origin in the request. * @throws Exception If some problem inside */ @Test - public void withoutOrigin() throws Exception { + public void handleConnectionsWithoutOriginInTheRequest() throws Exception { final Take take = new TkFake(new RsText()); final Take cors = new TkCORS(take); final RsPrint response = new RsPrint(cors.act(new RqFake())); final String head = response.printHead(); - this.httpStatusOk(head); + MatcherAssert.assertThat( + "Invalid HTTP status for a request without origin.", + head, + Matchers.containsString(Response.HTTP_STATUS_200) + ); } /** @@ -61,7 +66,7 @@ public void withoutOrigin() throws Exception { * @throws Exception If some problem inside */ @Test - public void withCorrectDomainOnOrigin() throws Exception { + public void handleConnectionsWithCorrectDomainOnOrigin() throws Exception { final Take take = new TkFake(new RsText()); final Take cors = new TkCORS( take, @@ -73,7 +78,11 @@ public void withCorrectDomainOnOrigin() throws Exception { final Request req = new RqWithHeaders(new RqFake(), headers); final RsPrint response = new RsPrint(cors.act(req)); final String head = response.printHead(); - this.httpStatusOk(head); + MatcherAssert.assertThat( + "Invalid HTTP status for a request with correct domain.", + head, + Matchers.containsString(Response.HTTP_STATUS_200) + ); } /** @@ -81,7 +90,8 @@ public void withCorrectDomainOnOrigin() throws Exception { * @throws Exception If some problem inside */ @Test - public void withWrongDomainOnOrigin() throws Exception { + public void cantHandleConnectionsWithWrongDomainOnOrigin() + throws Exception { final Take take = new TkFake(new RsText()); final Take cors = new TkCORS( take, @@ -104,17 +114,4 @@ public void withWrongDomainOnOrigin() throws Exception { Matchers.containsString("Access-Control-Allow-Credentials: false") ); } - - /** - * Checks if the given head contains text that identify it with HTTP 200 - * status. - * @param head HTTP response head. - */ - private void httpStatusOk(final String head) { - MatcherAssert.assertThat( - "Invalid HTTP status.", - head, - Matchers.containsString("HTTP/1.1 200 OK") - ); - } } From e9800ed7c6a23e8b8f380428d61fb14eb6663e8b Mon Sep 17 00:00:00 2001 From: Endrigo Antonini Date: Wed, 3 Jun 2015 00:33:40 -0300 Subject: [PATCH 10/48] Removed method isEmpty. --- .../java/org/takes/facets/auth/PsBasic.java | 28 ++++++++----------- 1 file changed, 11 insertions(+), 17 deletions(-) diff --git a/src/main/java/org/takes/facets/auth/PsBasic.java b/src/main/java/org/takes/facets/auth/PsBasic.java index c7bf85331..6800a7395 100644 --- a/src/main/java/org/takes/facets/auth/PsBasic.java +++ b/src/main/java/org/takes/facets/auth/PsBasic.java @@ -65,7 +65,7 @@ public PsBasic(final PsBasic.Entry basic) { @Override @SuppressWarnings("PMD.AvoidInstantiatingObjectsInLoops") public Opt enter(final Request request) throws IOException { - BasicAuth auth = new BasicAuth("", ""); + Opt auth = new Opt.Empty(); Opt identity = new Opt.Empty(); for (final String head : request.head()) { if (head.startsWith(AUTH_HEAD)) { @@ -73,15 +73,15 @@ public Opt enter(final Request request) throws IOException { break; } } - if (!auth.isEmpty() && this.entry.check( - auth.getUser(), - auth.getPass() + if (auth.has() && this.entry.check( + auth.get().getUser(), + auth.get().getPass() )) { final ConcurrentMap props = new ConcurrentHashMap(0); identity = new Opt.Single( new Identity.Simple( - String.format("urn:basic:%s", auth.getUser()), + String.format("urn:basic:%s", auth.get().getUser()), props ) ); @@ -100,16 +100,18 @@ public Response exit(final Response response, final Identity identity) * @param head Head * @return BasicAuth instance. */ - private BasicAuth readAuthContentOnHead(final String head) { + private Opt readAuthContentOnHead(final String head) { final String authorization = new String( DatatypeConverter.parseBase64Binary( head.split(AUTH_HEAD)[1].trim() ) ); final String user = authorization.split(":")[0]; - return new BasicAuth( - user, - authorization.substring(user.length() + 1) + return new Opt.Single( + new BasicAuth( + user, + authorization.substring(user.length() + 1) + ) ); } @@ -177,13 +179,5 @@ public String getUser() { public String getPass() { return this.pass; } - - /** - * Check if the object is empty. - * @return Return true if user and password is empty. - */ - public boolean isEmpty() { - return this.getUser().isEmpty() && this.getPass().isEmpty(); - } } } From 14588209014c6d7fca326a42977d70c46142674d Mon Sep 17 00:00:00 2001 From: Endrigo Antonini Date: Thu, 4 Jun 2015 15:08:48 -0300 Subject: [PATCH 11/48] #258 Removed one-time variables, removed null check, removed a class that already exists and also removed a constant that was created by mistake on Response. --- src/main/java/org/takes/Response.java | 6 -- src/main/java/org/takes/tk/TkCORS.java | 52 +++++++++-------- src/main/java/org/takes/tk/TkFake.java | 59 ------------------- src/test/java/org/takes/tk/TkCORSTest.java | 68 +++++++++++----------- 4 files changed, 62 insertions(+), 123 deletions(-) delete mode 100644 src/main/java/org/takes/tk/TkFake.java diff --git a/src/main/java/org/takes/Response.java b/src/main/java/org/takes/Response.java index 6ee5e7a6d..3a18aa631 100644 --- a/src/main/java/org/takes/Response.java +++ b/src/main/java/org/takes/Response.java @@ -55,12 +55,6 @@ */ public interface Response { - /** - * Http status 200. - *

HTTP/1.1 200 OK

- */ - String HTTP_STATUS_200 = "HTTP/1.1 200 OK"; - /** * HTTP response head. * @return Lines in HTTP response head diff --git a/src/main/java/org/takes/tk/TkCORS.java b/src/main/java/org/takes/tk/TkCORS.java index 0740d1fd6..01a4720a1 100644 --- a/src/main/java/org/takes/tk/TkCORS.java +++ b/src/main/java/org/takes/tk/TkCORS.java @@ -33,6 +33,7 @@ import org.takes.Request; import org.takes.Response; import org.takes.Take; +import org.takes.misc.Opt; import org.takes.rs.RsWithHeaders; import org.takes.rs.RsWithStatus; @@ -75,46 +76,49 @@ public final class TkCORS implements Take { */ public TkCORS(final Take take, final String... domains) { this.origin = take; - this.allowed = new HashSet(); - if (domains != null) { - this.allowed.addAll(Arrays.asList(domains)); - } + this.allowed = new HashSet(Arrays.asList(domains)); } @Override + @SuppressWarnings("PMD.AvoidInstantiatingObjectsInLoops") public Response act(final Request req) throws IOException { - boolean hasorigin = false; + Opt domain = new Opt.Empty(); final Response response; - String domain = ""; for (final String head : req.head()) { if (head.startsWith(ORIGIN_PREFIX)) { - hasorigin = true; - domain = head.split(ORIGIN_PREFIX)[1].trim(); + domain = new Opt.Single( + head.split(ORIGIN_PREFIX)[1].trim() + ); break; } } - if (hasorigin && ( + if (domain.has() && ( this.allowed.isEmpty() - || this.allowed.contains(domain))) { - final Set headers = new HashSet(); - headers.add("Access-Control-Allow-Credentials: true"); - headers.add( - // @checkstyle LineLengthCheck (1 line) - "Access-Control-Allow-Methods: OPTIONS, GET, PUT, POST, DELETE, HEAD" - ); - headers.add( - String.format("Access-Control-Allow-Origin: %s", domain) - ); - response = new RsWithHeaders(this.origin.act(req), headers); - } else if (hasorigin) { - final Set headers = Collections.singleton( - "Access-Control-Allow-Credentials: false" + || this.allowed.contains(domain.get()))) { + response = new RsWithHeaders( + this.origin.act(req), + new HashSet( + Arrays.asList( + new String[] { + "Access-Control-Allow-Credentials: true", + // @checkstyle LineLengthCheck (1 line) + "Access-Control-Allow-Methods: OPTIONS, GET, PUT, POST, DELETE, HEAD", + String.format( + "Access-Control-Allow-Origin: %s", + domain.get() + ), + } + ) + ) ); + } else if (domain.has()) { response = new RsWithHeaders( new RsWithStatus( HttpURLConnection.HTTP_FORBIDDEN ), - headers + Collections.singleton( + "Access-Control-Allow-Credentials: false" + ) ); } else { response = this.origin.act(req); diff --git a/src/main/java/org/takes/tk/TkFake.java b/src/main/java/org/takes/tk/TkFake.java deleted file mode 100644 index 6a4a78807..000000000 --- a/src/main/java/org/takes/tk/TkFake.java +++ /dev/null @@ -1,59 +0,0 @@ -/** - * The MIT License (MIT) - * - * Copyright (c) 2015 Yegor Bugayenko - * - * Permission is hereby granted, free of charge, to any person obtaining a copy - * of this software and associated documentation files (the "Software"), to deal - * in the Software without restriction, including without limitation the rights - * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell - * copies of the Software, and to permit persons to whom the Software is - * furnished to do so, subject to the following conditions: - * - * The above copyright notice and this permission notice shall be included - * in all copies or substantial portions of the Software. - * - * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR - * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, - * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE - * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER - * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, - * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE - * SOFTWARE. - */ -package org.takes.tk; - -import java.io.IOException; -import lombok.EqualsAndHashCode; -import org.takes.Request; -import org.takes.Response; -import org.takes.Take; - -/** - * Fake Take. - * - * @author Endrigo Antonini (teamed@endrigo.com.br) - * @version $Id$ - * @since 0.20 - */ -@EqualsAndHashCode(of = { "response" }) -public final class TkFake implements Take { - - /** - * Response. - */ - private final transient Response response; - - /** - * Ctor. - * @param resp Response - */ - public TkFake(final Response resp) { - this.response = resp; - } - - @Override - public Response act(final Request request) throws IOException { - return this.response; - } -} diff --git a/src/test/java/org/takes/tk/TkCORSTest.java b/src/test/java/org/takes/tk/TkCORSTest.java index 9b75dfd39..777fdedb9 100644 --- a/src/test/java/org/takes/tk/TkCORSTest.java +++ b/src/test/java/org/takes/tk/TkCORSTest.java @@ -23,14 +23,13 @@ */ package org.takes.tk; +import java.net.HttpURLConnection; +import java.util.Arrays; import java.util.HashSet; -import java.util.Set; import org.hamcrest.MatcherAssert; import org.hamcrest.Matchers; import org.junit.Test; -import org.takes.Request; -import org.takes.Response; -import org.takes.Take; +import org.takes.facets.hamcrest.HmRsStatus; import org.takes.rq.RqFake; import org.takes.rq.RqWithHeaders; import org.takes.rs.RsPrint; @@ -50,14 +49,10 @@ public final class TkCORSTest { */ @Test public void handleConnectionsWithoutOriginInTheRequest() throws Exception { - final Take take = new TkFake(new RsText()); - final Take cors = new TkCORS(take); - final RsPrint response = new RsPrint(cors.act(new RqFake())); - final String head = response.printHead(); MatcherAssert.assertThat( "Invalid HTTP status for a request without origin.", - head, - Matchers.containsString(Response.HTTP_STATUS_200) + new TkCORS(new TkFixed(new RsText())).act(new RqFake()), + new HmRsStatus(Matchers.equalTo(HttpURLConnection.HTTP_OK)) ); } @@ -67,21 +62,23 @@ public void handleConnectionsWithoutOriginInTheRequest() throws Exception { */ @Test public void handleConnectionsWithCorrectDomainOnOrigin() throws Exception { - final Take take = new TkFake(new RsText()); - final Take cors = new TkCORS( - take, - "http://teamed.io", - "http://example.com" - ); - final Set headers = new HashSet(); - headers.add("Origin: http://teamed.io"); - final Request req = new RqWithHeaders(new RqFake(), headers); - final RsPrint response = new RsPrint(cors.act(req)); - final String head = response.printHead(); MatcherAssert.assertThat( "Invalid HTTP status for a request with correct domain.", - head, - Matchers.containsString(Response.HTTP_STATUS_200) + new TkCORS( + new TkFixed(new RsText()), + "http://teamed.io", + "http://example.com" + ).act( + new RqWithHeaders( + new RqFake(), + new HashSet( + Arrays.asList( + "Origin: http://teamed.io" + ) + ) + ) + ), + new HmRsStatus(Matchers.equalTo(HttpURLConnection.HTTP_OK)) ); } @@ -92,17 +89,20 @@ public void handleConnectionsWithCorrectDomainOnOrigin() throws Exception { @Test public void cantHandleConnectionsWithWrongDomainOnOrigin() throws Exception { - final Take take = new TkFake(new RsText()); - final Take cors = new TkCORS( - take, - "http://www.teamed.io", - "http://sample.com" - ); - final Set headers = new HashSet(); - headers.add("Origin: http://wrong.teamed.io"); - final Request req = new RqWithHeaders(new RqFake(), headers); - final RsPrint response = new RsPrint(cors.act(req)); - final String head = response.printHead(); + final String head = new RsPrint( + new TkCORS( + new TkFixed(new RsText()), + "http://www.teamed.io", + "http://sample.com" + ).act( + new RqWithHeaders( + new RqFake(), + new HashSet( + Arrays.asList("Origin: http://wrong.teamed.io") + ) + ) + ) + ).printHead(); MatcherAssert.assertThat( "It was expected a 403 HTTP status.", head, From 9ecce2f96346c356c6d2794e27f03a5bd0fa6f55 Mon Sep 17 00:00:00 2001 From: Endrigo Antonini Date: Thu, 4 Jun 2015 16:36:02 -0300 Subject: [PATCH 12/48] #258 Fixed some mistakes that made the code over-complicated. --- src/main/java/org/takes/tk/TkCORS.java | 23 +++++++--------------- src/test/java/org/takes/tk/TkCORSTest.java | 6 +----- 2 files changed, 8 insertions(+), 21 deletions(-) diff --git a/src/main/java/org/takes/tk/TkCORS.java b/src/main/java/org/takes/tk/TkCORS.java index 01a4720a1..bcb4968af 100644 --- a/src/main/java/org/takes/tk/TkCORS.java +++ b/src/main/java/org/takes/tk/TkCORS.java @@ -26,7 +26,6 @@ import java.io.IOException; import java.net.HttpURLConnection; import java.util.Arrays; -import java.util.Collections; import java.util.HashSet; import java.util.Set; import lombok.EqualsAndHashCode; @@ -97,18 +96,12 @@ public Response act(final Request req) throws IOException { || this.allowed.contains(domain.get()))) { response = new RsWithHeaders( this.origin.act(req), - new HashSet( - Arrays.asList( - new String[] { - "Access-Control-Allow-Credentials: true", - // @checkstyle LineLengthCheck (1 line) - "Access-Control-Allow-Methods: OPTIONS, GET, PUT, POST, DELETE, HEAD", - String.format( - "Access-Control-Allow-Origin: %s", - domain.get() - ), - } - ) + "Access-Control-Allow-Credentials: true", + // @checkstyle LineLengthCheck (1 line) + "Access-Control-Allow-Methods: OPTIONS, GET, PUT, POST, DELETE, HEAD", + String.format( + "Access-Control-Allow-Origin: %s", + domain.get() ) ); } else if (domain.has()) { @@ -116,9 +109,7 @@ public Response act(final Request req) throws IOException { new RsWithStatus( HttpURLConnection.HTTP_FORBIDDEN ), - Collections.singleton( - "Access-Control-Allow-Credentials: false" - ) + "Access-Control-Allow-Credentials: false" ); } else { response = this.origin.act(req); diff --git a/src/test/java/org/takes/tk/TkCORSTest.java b/src/test/java/org/takes/tk/TkCORSTest.java index 777fdedb9..f4f63a852 100644 --- a/src/test/java/org/takes/tk/TkCORSTest.java +++ b/src/test/java/org/takes/tk/TkCORSTest.java @@ -71,11 +71,7 @@ public void handleConnectionsWithCorrectDomainOnOrigin() throws Exception { ).act( new RqWithHeaders( new RqFake(), - new HashSet( - Arrays.asList( - "Origin: http://teamed.io" - ) - ) + "Origin: http://teamed.io" ) ), new HmRsStatus(Matchers.equalTo(HttpURLConnection.HTTP_OK)) From 03a65fa06db40ac4c530aa66ae3b1c277d46836c Mon Sep 17 00:00:00 2001 From: Endrigo Antonini Date: Thu, 4 Jun 2015 17:15:55 -0300 Subject: [PATCH 13/48] #221 Fixed Entry interface to use Opt and also improved design. --- .../java/org/takes/facets/auth/PsBasic.java | 79 ++++++++++++++++--- .../org/takes/facets/auth/PsBasicTest.java | 28 +------ 2 files changed, 73 insertions(+), 34 deletions(-) diff --git a/src/main/java/org/takes/facets/auth/PsBasic.java b/src/main/java/org/takes/facets/auth/PsBasic.java index 6800a7395..c3facfe57 100644 --- a/src/main/java/org/takes/facets/auth/PsBasic.java +++ b/src/main/java/org/takes/facets/auth/PsBasic.java @@ -74,14 +74,14 @@ public Opt enter(final Request request) throws IOException { } } if (auth.has() && this.entry.check( - auth.get().getUser(), - auth.get().getPass() - )) { + auth.get().username(), + auth.get().password() + ).has()) { final ConcurrentMap props = new ConcurrentHashMap(0); identity = new Opt.Single( new Identity.Simple( - String.format("urn:basic:%s", auth.get().getUser()), + String.format("urn:basic:%s", auth.get().username()), props ) ); @@ -129,9 +129,68 @@ public interface Entry { * Check if is a valid user. * @param user User * @param pwd Password - * @return If valid it return true. + * @return Identity. */ - boolean check(String user, String pwd); + Opt check(String user, String pwd); + } + + /** + * Entry with fixed credentials. + * + * @author Endrigo Antonini (teamed@endrigo.com.br) + * @version $Id$ + * @since 0.20 + * + */ + public static final class Fixed implements PsBasic.Entry { + + /** + * Username. + */ + private final transient String username; + + /** + * Password. + */ + private final transient String password; + + /** + * Ctor. + * @param user Username. + * @param pwd Password. + */ + public Fixed(final String user, final String pwd) { + this.username = user; + this.password = pwd; + } + + @Override + public Opt check(final String user, final String pwd) { + final Opt identity; + if (this.username.equals(user) && this.password.equals(pwd)) { + identity = new Opt.Single( + new Identity.Simple(user) + ); + } else { + identity = new Opt.Empty(); + } + return identity; + } + } + + /** + * Empty check. + * + * @author Endrigo Antonini (teamed@endrigo.com.br) + * @version $Id$ + * @since 0.20 + */ + public static final class Empty implements PsBasic.Entry { + + @Override + public Opt check(final String user, final String pwd) { + return new Opt.Empty(); + } } /** @@ -146,12 +205,12 @@ private final class BasicAuth { /** * User. */ - private final String user; + private final transient String user; /** * Password. */ - private final String pass; + private final transient String pass; /** * Ctor. @@ -168,7 +227,7 @@ public BasicAuth(final String username, final String password) { * Return user. * @return User. */ - public String getUser() { + public String username() { return this.user; } @@ -176,7 +235,7 @@ public String getUser() { * Return Password. * @return Password. */ - public String getPass() { + public String password() { return this.pass; } } diff --git a/src/test/java/org/takes/facets/auth/PsBasicTest.java b/src/test/java/org/takes/facets/auth/PsBasicTest.java index 7effed5b7..3df7593dc 100644 --- a/src/test/java/org/takes/facets/auth/PsBasicTest.java +++ b/src/test/java/org/takes/facets/auth/PsBasicTest.java @@ -67,12 +67,7 @@ public void handleConnectionWithValidCredential() throws Exception { this.generateAuthenticateHead(user, pass) ); final Opt identity = new PsBasic( - new PsBasic.Entry() { - @Override - public boolean check(final String username, final String pwd) { - return user.equals(username) && pass.equals(pwd); - } - } + new PsBasic.Fixed(user, pass) ).enter(req); MatcherAssert.assertThat(identity.has(), Matchers.is(true)); MatcherAssert.assertThat( @@ -99,12 +94,7 @@ public void handleConnectionWithInvalidCredential() throws Exception { this.generateAuthenticateHead("username", "wrong") ); final Opt identity = new PsBasic( - new PsBasic.Entry() { - @Override - public boolean check(final String user, final String pwd) { - return false; - } - } + new PsBasic.Empty() ).enter(req); MatcherAssert.assertThat(identity.has(), Matchers.is(false)); } @@ -134,12 +124,7 @@ public void handleMultipleHeadersWithValidCredential() throws Exception { "X-Powered-By:Java/1.7" ); final Opt identity = new PsBasic( - new PsBasic.Entry() { - @Override - public boolean check(final String username, final String pwd) { - return user.equals(username) && pass.equals(pwd); - } - } + new PsBasic.Fixed(user, pass) ).enter(req); MatcherAssert.assertThat(identity.has(), Matchers.is(true)); MatcherAssert.assertThat( @@ -169,12 +154,7 @@ public void handleMultipleHeadersWithInvalidContent() throws Exception { "XYZX-Powered-By:Java/1.7" ); final Opt identity = new PsBasic( - new PsBasic.Entry() { - @Override - public boolean check(final String username, final String pwd) { - return user.equals(username) && pass.equals(pwd); - } - } + new PsBasic.Fixed(user, pass) ).enter(req); MatcherAssert.assertThat(identity.has(), Matchers.is(false)); } From d6b961cdc729385dcf4da94db06a7f09bbe908cf Mon Sep 17 00:00:00 2001 From: Endrigo Antonini Date: Thu, 4 Jun 2015 18:20:05 -0300 Subject: [PATCH 14/48] #258 Improved unit test and changed to use RqHeaders. --- src/main/java/org/takes/tk/TkCORS.java | 22 +++++------- src/test/java/org/takes/tk/TkCORSTest.java | 39 ++++++++++------------ 2 files changed, 25 insertions(+), 36 deletions(-) diff --git a/src/main/java/org/takes/tk/TkCORS.java b/src/main/java/org/takes/tk/TkCORS.java index bcb4968af..456218cad 100644 --- a/src/main/java/org/takes/tk/TkCORS.java +++ b/src/main/java/org/takes/tk/TkCORS.java @@ -33,6 +33,7 @@ import org.takes.Response; import org.takes.Take; import org.takes.misc.Opt; +import org.takes.rq.RqHeaders; import org.takes.rs.RsWithHeaders; import org.takes.rs.RsWithStatus; @@ -53,11 +54,6 @@ @EqualsAndHashCode(of = { "origin" , "allowed" }) public final class TkCORS implements Take { - /** - * Prefix of HTTP head Origin. - */ - private static final String ORIGIN_PREFIX = "Origin:"; - /** * Original take. */ @@ -79,17 +75,15 @@ public TkCORS(final Take take, final String... domains) { } @Override - @SuppressWarnings("PMD.AvoidInstantiatingObjectsInLoops") public Response act(final Request req) throws IOException { - Opt domain = new Opt.Empty(); + Opt domain; final Response response; - for (final String head : req.head()) { - if (head.startsWith(ORIGIN_PREFIX)) { - domain = new Opt.Single( - head.split(ORIGIN_PREFIX)[1].trim() - ); - break; - } + try { + domain = new Opt.Single( + new RqHeaders.Smart(new RqHeaders.Base(req)).single("origin") + ); + } catch (final IOException exp) { + domain = new Opt.Empty(); } if (domain.has() && ( this.allowed.isEmpty() diff --git a/src/test/java/org/takes/tk/TkCORSTest.java b/src/test/java/org/takes/tk/TkCORSTest.java index f4f63a852..432aa82a0 100644 --- a/src/test/java/org/takes/tk/TkCORSTest.java +++ b/src/test/java/org/takes/tk/TkCORSTest.java @@ -24,8 +24,6 @@ package org.takes.tk; import java.net.HttpURLConnection; -import java.util.Arrays; -import java.util.HashSet; import org.hamcrest.MatcherAssert; import org.hamcrest.Matchers; import org.junit.Test; @@ -85,29 +83,26 @@ public void handleConnectionsWithCorrectDomainOnOrigin() throws Exception { @Test public void cantHandleConnectionsWithWrongDomainOnOrigin() throws Exception { - final String head = new RsPrint( - new TkCORS( - new TkFixed(new RsText()), - "http://www.teamed.io", - "http://sample.com" - ).act( - new RqWithHeaders( - new RqFake(), - new HashSet( - Arrays.asList("Origin: http://wrong.teamed.io") + MatcherAssert.assertThat( + "Wrong value on header.", + new RsPrint( + new TkCORS( + new TkFixed(new RsText()), + "http://www.teamed.io", + "http://sample.com" + ).act( + new RqWithHeaders( + new RqFake(), + "Origin: http://wrong.teamed.io" ) ) + ).printHead(), + Matchers.allOf( + Matchers.containsString("HTTP/1.1 403"), + Matchers.containsString( + "Access-Control-Allow-Credentials: false" + ) ) - ).printHead(); - MatcherAssert.assertThat( - "It was expected a 403 HTTP status.", - head, - Matchers.containsString("HTTP/1.1 403") - ); - MatcherAssert.assertThat( - "Wrong value on Access Control Allow Credentias param.", - head, - Matchers.containsString("Access-Control-Allow-Credentials: false") ); } } From e652e65dd026394831ab226a0baa40ec67cfad8c Mon Sep 17 00:00:00 2001 From: Endrigo Antonini Date: Thu, 4 Jun 2015 22:36:04 -0300 Subject: [PATCH 15/48] #258 Removed code complexity. --- src/main/java/org/takes/tk/TkCORS.java | 21 ++++++++------------- src/test/java/org/takes/tk/TkCORSTest.java | 10 +++++++--- 2 files changed, 15 insertions(+), 16 deletions(-) diff --git a/src/main/java/org/takes/tk/TkCORS.java b/src/main/java/org/takes/tk/TkCORS.java index 456218cad..29856187c 100644 --- a/src/main/java/org/takes/tk/TkCORS.java +++ b/src/main/java/org/takes/tk/TkCORS.java @@ -32,7 +32,6 @@ import org.takes.Request; import org.takes.Response; import org.takes.Take; -import org.takes.misc.Opt; import org.takes.rq.RqHeaders; import org.takes.rs.RsWithHeaders; import org.takes.rs.RsWithStatus; @@ -76,18 +75,16 @@ public TkCORS(final Take take, final String... domains) { @Override public Response act(final Request req) throws IOException { - Opt domain; + String domain; final Response response; try { - domain = new Opt.Single( - new RqHeaders.Smart(new RqHeaders.Base(req)).single("origin") - ); + domain = new RqHeaders.Smart( + new RqHeaders.Base(req) + ).single("origin"); } catch (final IOException exp) { - domain = new Opt.Empty(); + domain = ""; } - if (domain.has() && ( - this.allowed.isEmpty() - || this.allowed.contains(domain.get()))) { + if (this.allowed.contains(domain)) { response = new RsWithHeaders( this.origin.act(req), "Access-Control-Allow-Credentials: true", @@ -95,18 +92,16 @@ public Response act(final Request req) throws IOException { "Access-Control-Allow-Methods: OPTIONS, GET, PUT, POST, DELETE, HEAD", String.format( "Access-Control-Allow-Origin: %s", - domain.get() + domain ) ); - } else if (domain.has()) { + } else { response = new RsWithHeaders( new RsWithStatus( HttpURLConnection.HTTP_FORBIDDEN ), "Access-Control-Allow-Credentials: false" ); - } else { - response = this.origin.act(req); } return response; } diff --git a/src/test/java/org/takes/tk/TkCORSTest.java b/src/test/java/org/takes/tk/TkCORSTest.java index 432aa82a0..98896e25c 100644 --- a/src/test/java/org/takes/tk/TkCORSTest.java +++ b/src/test/java/org/takes/tk/TkCORSTest.java @@ -48,9 +48,13 @@ public final class TkCORSTest { @Test public void handleConnectionsWithoutOriginInTheRequest() throws Exception { MatcherAssert.assertThat( - "Invalid HTTP status for a request without origin.", - new TkCORS(new TkFixed(new RsText())).act(new RqFake()), - new HmRsStatus(Matchers.equalTo(HttpURLConnection.HTTP_OK)) + "It was expected to receive a 403 error.", + new TkCORS( + new TkFixed(new RsText()), + "http://www.netbout.io", + "http://www.example.com" + ).act(new RqFake()), + new HmRsStatus(Matchers.equalTo(HttpURLConnection.HTTP_FORBIDDEN)) ); } From 9533a4789b7206b41d73160b57ae1d60c784ef9e Mon Sep 17 00:00:00 2001 From: Endrigo Antonini Date: Thu, 4 Jun 2015 23:29:51 -0300 Subject: [PATCH 16/48] #221 Removed code complexity. --- .../java/org/takes/facets/auth/PsBasic.java | 111 ++++-------------- 1 file changed, 22 insertions(+), 89 deletions(-) diff --git a/src/main/java/org/takes/facets/auth/PsBasic.java b/src/main/java/org/takes/facets/auth/PsBasic.java index c3facfe57..49821dca9 100644 --- a/src/main/java/org/takes/facets/auth/PsBasic.java +++ b/src/main/java/org/takes/facets/auth/PsBasic.java @@ -24,13 +24,12 @@ package org.takes.facets.auth; import java.io.IOException; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.ConcurrentMap; import javax.xml.bind.DatatypeConverter; import lombok.EqualsAndHashCode; import org.takes.Request; import org.takes.Response; import org.takes.misc.Opt; +import org.takes.rq.RqHeaders; /** * Pass that checks the user according RFC-2617. @@ -47,7 +46,7 @@ public final class PsBasic implements Pass { /** * Authorization response HTTP head. */ - private static final String AUTH_HEAD = "Authorization: Basic"; + private static final String AUTH_HEAD = "Basic"; /** * Entry to validate user information. @@ -63,28 +62,29 @@ public PsBasic(final PsBasic.Entry basic) { } @Override - @SuppressWarnings("PMD.AvoidInstantiatingObjectsInLoops") public Opt enter(final Request request) throws IOException { - Opt auth = new Opt.Empty(); Opt identity = new Opt.Empty(); - for (final String head : request.head()) { - if (head.startsWith(AUTH_HEAD)) { - auth = this.readAuthContentOnHead(head); - break; - } - } - if (auth.has() && this.entry.check( - auth.get().username(), - auth.get().password() - ).has()) { - final ConcurrentMap props = - new ConcurrentHashMap(0); - identity = new Opt.Single( - new Identity.Simple( - String.format("urn:basic:%s", auth.get().username()), - props + try { + final String decoded = new String( + DatatypeConverter.parseBase64Binary( + new RqHeaders.Smart( + new RqHeaders.Base(request) + ).single("authorization").split(AUTH_HEAD)[1] ) - ); + ).trim(); + final String user = decoded.split(":")[0]; + if (this.entry.check( + user, + decoded.substring(user.length() + 1) + ).has()) { + identity = new Opt.Single( + new Identity.Simple( + String.format("urn:basic:%s", user) + ) + ); + } + } catch (final IOException ex) { + identity = new Opt.Empty(); } return identity; } @@ -95,26 +95,6 @@ public Response exit(final Response response, final Identity identity) return response; } - /** - * Read authentication content that is received on the head. - * @param head Head - * @return BasicAuth instance. - */ - private Opt readAuthContentOnHead(final String head) { - final String authorization = new String( - DatatypeConverter.parseBase64Binary( - head.split(AUTH_HEAD)[1].trim() - ) - ); - final String user = authorization.split(":")[0]; - return new Opt.Single( - new BasicAuth( - user, - authorization.substring(user.length() + 1) - ) - ); - } - /** * Entry interface that is used to check if the received information is * valid. @@ -192,51 +172,4 @@ public Opt check(final String user, final String pwd) { return new Opt.Empty(); } } - - /** - * Used to transfer authentication information. - * - * @author Endrigo Antonini (teamed@endrigo.com.br) - * @version $Id$ - * @since 0.20 - */ - private final class BasicAuth { - - /** - * User. - */ - private final transient String user; - - /** - * Password. - */ - private final transient String pass; - - /** - * Ctor. - * @param username User - * @param password Password - */ - public BasicAuth(final String username, final String password) { - super(); - this.user = username; - this.pass = password; - } - - /** - * Return user. - * @return User. - */ - public String username() { - return this.user; - } - - /** - * Return Password. - * @return Password. - */ - public String password() { - return this.pass; - } - } } From 54f0dc640d3d276336a47735d4573df6fcaa953f Mon Sep 17 00:00:00 2001 From: Endrigo Antonini Date: Fri, 5 Jun 2015 21:40:12 -0300 Subject: [PATCH 17/48] #258 Added a puzzle to create `single` method that receive de key and the default value and return it if the key is not found. --- src/main/java/org/takes/tk/TkCORS.java | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/main/java/org/takes/tk/TkCORS.java b/src/main/java/org/takes/tk/TkCORS.java index 29856187c..b655343f6 100644 --- a/src/main/java/org/takes/tk/TkCORS.java +++ b/src/main/java/org/takes/tk/TkCORS.java @@ -78,6 +78,12 @@ public Response act(final Request req) throws IOException { String domain; final Response response; try { + // @checkstyle MethodBodyCommentsCheck (6 lines) + // @todo #258:30min RqHeaders.Smart should have a `single(string, + // string)` method. That method should be similar to the method + // found in `RqHref.Smart`). The first parameter is the key that you + // are looking for and the second one is the default value if it was + // not found. Don't forget to remove the checkstyle comment. domain = new RqHeaders.Smart( new RqHeaders.Base(req) ).single("origin"); From 2f5a385431d42ce48c970601f484ffacf64f7821 Mon Sep 17 00:00:00 2001 From: Lautaro Cozzani Date: Mon, 8 Jun 2015 11:26:59 -0300 Subject: [PATCH 18/48] #263: extract method and make cacheable --- src/main/java/org/takes/rq/RqForm.java | 77 ++++++++++++++++---------- 1 file changed, 47 insertions(+), 30 deletions(-) diff --git a/src/main/java/org/takes/rq/RqForm.java b/src/main/java/org/takes/rq/RqForm.java index 281c39685..2efc7aed7 100644 --- a/src/main/java/org/takes/rq/RqForm.java +++ b/src/main/java/org/takes/rq/RqForm.java @@ -23,6 +23,7 @@ */ package org.takes.rq; +import com.jcabi.aspects.Cacheable; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; @@ -82,53 +83,35 @@ public interface RqForm extends Request { * @author Aleksey Popov (alopen@yandex.ru) * @version $Id$ */ - @EqualsAndHashCode(callSuper = true, of = "map") + @EqualsAndHashCode(callSuper = true, of = "req") final class Base extends RqWrap implements RqForm { + /** - * Map of params and values. + * Request. */ - private final transient ConcurrentMap> map; + private final transient Request req; + /** * Ctor. - * @param req Original request + * @param request Original request * @throws IOException If fails */ @SuppressWarnings("PMD.AvoidInstantiatingObjectsInLoops") - public Base(final Request req) throws IOException { - super(req); - final ByteArrayOutputStream baos = new ByteArrayOutputStream(); - new RqPrint(req).printBody(baos); - final String body = new String(baos.toByteArray()); - this.map = new ConcurrentHashMap>(0); - for (final String pair : body.split("&")) { - if (pair.isEmpty()) { - continue; - } - final String[] parts = pair.split("=", 2); - if (parts.length < 2) { - throw new HttpException( - HttpURLConnection.HTTP_BAD_REQUEST, - String.format("invalid form body pair: %s", pair) - ); - } - final String key = RqForm.Base.decode( - parts[0].trim().toLowerCase(Locale.ENGLISH) - ); - this.map.putIfAbsent(key, new LinkedList()); - this.map.get(key).add(RqForm.Base.decode(parts[1].trim())); - } + public Base(final Request request) throws IOException { + super(request); + this.req = request; } @Override public Iterable param(final CharSequence key) { final List values = - this.map.get(key.toString().toLowerCase(Locale.ENGLISH)); + this.map().get(key.toString().toLowerCase(Locale.ENGLISH)); final Iterable iter; if (values == null) { iter = new VerboseIterable( Collections.emptyList(), new Sprintf( "there are no params \"%s\" among %d others: %s", - key, this.map.size(), this.map.keySet() + key, this.map().size(), this.map().keySet() ) ); } else { @@ -144,7 +127,7 @@ public Iterable param(final CharSequence key) { } @Override public Iterable names() { - return this.map.keySet(); + return this.map().keySet(); } /** * Decode from URL. @@ -160,6 +143,40 @@ private static String decode(final CharSequence txt) { throw new IllegalStateException(ex); } } + /** + * Create map of request parameter. + * @return Parameters map or empty map in case of error. + */ + @Cacheable(forever = true) + private ConcurrentMap> map() { + try { + final ByteArrayOutputStream baos = new ByteArrayOutputStream(); + new RqPrint(this.req).printBody(baos); + final String body = new String(baos.toByteArray()); + final ConcurrentMap> map = + new ConcurrentHashMap>(0); + for (final String pair : body.split("&")) { + if (pair.isEmpty()) { + continue; + } + final String[] parts = pair.split("=", 2); + if (parts.length < 2) { + throw new HttpException( + HttpURLConnection.HTTP_BAD_REQUEST, + String.format("invalid form body pair: %s", pair) + ); + } + final String key = RqForm.Base.decode( + parts[0].trim().toLowerCase(Locale.ENGLISH) + ); + map.putIfAbsent(key, new LinkedList()); + map.get(key).add(RqForm.Base.decode(parts[1].trim())); + } + return map; + } catch (final IOException ex) { + return new ConcurrentHashMap>(0); + } + } } /** * Smart decorator, with extra features. From 8c97d0b92a9c8b89e11f164a2c5e29c0c9460c17 Mon Sep 17 00:00:00 2001 From: Adam Siemion Date: Mon, 8 Jun 2015 17:00:11 +0200 Subject: [PATCH 19/48] #230 introduced code review fixes --- .../facets/auth/social/PsTwitterTest.java | 22 ++++++------------- 1 file changed, 7 insertions(+), 15 deletions(-) diff --git a/src/test/java/org/takes/facets/auth/social/PsTwitterTest.java b/src/test/java/org/takes/facets/auth/social/PsTwitterTest.java index c94552019..9b446027e 100644 --- a/src/test/java/org/takes/facets/auth/social/PsTwitterTest.java +++ b/src/test/java/org/takes/facets/auth/social/PsTwitterTest.java @@ -49,14 +49,14 @@ public final class PsTwitterTest { /** - * Twitter authorization process. + * PsTwitter can login. * @throws IOException If error occurs in the process */ @Test - public void canLogin() throws IOException { + public void logsIn() throws IOException { final int tid = RandomUtils.nextInt(1000); - final String name = this.randomAlphanumeric(); - final String picture = this.randomAlphanumeric(); + final String name = RandomStringUtils.randomAlphanumeric(10); + final String picture = RandomStringUtils.randomAlphanumeric(10); final Pass pass = new PsTwitter( new FakeRequest( 200, @@ -64,7 +64,7 @@ public void canLogin() throws IOException { Collections.>emptyList(), String.format( "{\"token_type\":\"bearer\",\"access_token\":\"%s\"}", - this.randomAlphanumeric() + RandomStringUtils.randomAlphanumeric(10) ).getBytes() ), new FakeRequest( @@ -79,8 +79,8 @@ public void canLogin() throws IOException { .toString() .getBytes() ), - this.randomAlphanumeric(), - this.randomAlphanumeric() + RandomStringUtils.randomAlphanumeric(10), + RandomStringUtils.randomAlphanumeric(10) ); final Identity identity = pass.enter( new RqFake("GET", "") @@ -98,12 +98,4 @@ public void canLogin() throws IOException { CoreMatchers.equalTo(picture) ); } - - /** - * Return a random alphanumeric string. - * @return Random alphanumeric string - */ - private String randomAlphanumeric() { - return RandomStringUtils.randomAlphanumeric(10); - } } From e83393da8a4a252a273693656dd51bfda8cab063 Mon Sep 17 00:00:00 2001 From: Adam Siemion Date: Mon, 8 Jun 2015 18:10:57 +0200 Subject: [PATCH 20/48] #230 code review fixes --- .../takes/facets/auth/social/PsTwitter.java | 78 +++++++++++-------- 1 file changed, 46 insertions(+), 32 deletions(-) diff --git a/src/main/java/org/takes/facets/auth/social/PsTwitter.java b/src/main/java/org/takes/facets/auth/social/PsTwitter.java index 836e0ab91..58f6802ff 100644 --- a/src/main/java/org/takes/facets/auth/social/PsTwitter.java +++ b/src/main/java/org/takes/facets/auth/social/PsTwitter.java @@ -71,36 +71,36 @@ public final class PsTwitter implements Pass { private final transient String key; /** - * Request for fetching app token. + * Request for fetching app trequest. */ private final transient com.jcabi.http.Request trequest; /** * Request for verifying user credentials. */ - private final transient com.jcabi.http.Request vcrequest; + private final transient com.jcabi.http.Request user; /** * Ctor. - * @param tapp Twitter app - * @param tkey Twitter key + * @param name Twitter app + * @param keys Twitter key */ - public PsTwitter(final String tapp, final String tkey) { + public PsTwitter(final String name, final String keys) { this( new JdkRequest( - new Href("https://api.twitter.com/oauth2/token") + new Href("https://api.twitter.com/oauth2/trequest") .with("grant_type", "client_credentials") .toString() ), - new JdkRequest(VERIFY_URL), - tapp, - tkey + new JdkRequest(PsTwitter.VERIFY_URL), + name, + keys ); } /** * Ctor with proper requestor for testing purposes. - * @param ttrequest HTTP request for getting token + * @param ttrequest HTTP request for getting trequest * @param tvcrequest HTTP request for verifying credentials * @param tapp Facebook app * @param tkey Facebook key @@ -111,7 +111,7 @@ public PsTwitter(final String tapp, final String tkey) { final String tapp, final String tkey) { this.trequest = ttrequest; - this.vcrequest = tvcrequest; + this.user = tvcrequest; this.app = tapp; this.key = tkey; } @@ -128,38 +128,51 @@ public Response exit(final Response response, final Identity identity) { } /** - * Get user name from Twitter, with the token provided. - * @param token Twitter access token + * Get user name from Twitter, with the trequest provided. + * @param token Twitter access trequest * @return The user found in Twitter * @throws IOException If fails */ private Identity fetch(final String token) throws IOException { - final JsonObject response = this.vcrequest - .uri() - .set( - URI.create( - new Href(VERIFY_URL) - .with("access_token", token) - .toString() + return parse( + this.user + .uri() + .set( + URI.create( + new Href(PsTwitter.VERIFY_URL) + .with("access_token", token) + .toString() + ) ) - ) - .back() - .header("accept", "application/json") - .fetch().as(RestResponse.class) - .assertStatus(HttpURLConnection.HTTP_OK) - .as(JsonResponse.class).json().readObject(); + .back() + .header("accept", "application/json") + .fetch().as(RestResponse.class) + .assertStatus(HttpURLConnection.HTTP_OK) + .as(JsonResponse.class) + .json() + .readObject() + ); + } + + /** + * Make identity from JSON object. + * @param json JSON received from Twitter + * @return Identity found + */ + private static Identity parse(final JsonObject json) { final ConcurrentMap props = - new ConcurrentHashMap(response.size()); - props.put("name", response.getString("name")); - props.put("picture", response.getString("profile_image_url")); + new ConcurrentHashMap(json.size()); + props.put("name", json.getString("name")); + props.put("picture", json.getString("profile_image_url")); return new Identity.Simple( - String.format("urn:twitter:%d", response.getInt("id")), props + String.format("urn:twitter:%d", json.getInt("id")), + props ); } /** - * Retrieve Twitter access token. - * @return The token + * Retrieve Twitter access trequest. + * @return The trequest * @throws IOException If failed */ private String token() throws IOException { @@ -182,4 +195,5 @@ private String token() throws IOException { .as(JsonResponse.class) .json().readObject().getString("access_token"); } + } From 7f28c99c0b79f34431a7d26e0b36a5d2d2bdc34e Mon Sep 17 00:00:00 2001 From: Endrigo Antonini Date: Mon, 8 Jun 2015 23:40:08 -0300 Subject: [PATCH 21/48] #258 Fixed todo indentation. --- src/main/java/org/takes/tk/TkCORS.java | 11 ++++++----- 1 file changed, 6 insertions(+), 5 deletions(-) diff --git a/src/main/java/org/takes/tk/TkCORS.java b/src/main/java/org/takes/tk/TkCORS.java index b655343f6..01c410095 100644 --- a/src/main/java/org/takes/tk/TkCORS.java +++ b/src/main/java/org/takes/tk/TkCORS.java @@ -78,12 +78,13 @@ public Response act(final Request req) throws IOException { String domain; final Response response; try { - // @checkstyle MethodBodyCommentsCheck (6 lines) + // @checkstyle MethodBodyCommentsCheck (7 lines) // @todo #258:30min RqHeaders.Smart should have a `single(string, - // string)` method. That method should be similar to the method - // found in `RqHref.Smart`). The first parameter is the key that you - // are looking for and the second one is the default value if it was - // not found. Don't forget to remove the checkstyle comment. + // string)` method. That method should be similar to the method + // found in `RqHref.Smart`). The first parameter is the key that + // you are looking for and the second one is the default value if + // it was not found. Don't forget to remove the checkstyle + // comment. domain = new RqHeaders.Smart( new RqHeaders.Base(req) ).single("origin"); From 61a1e9bcab06c22374b617367459f1a60f5bbbe4 Mon Sep 17 00:00:00 2001 From: Eugene Kondrashev Date: Fri, 5 Jun 2015 22:20:07 +0300 Subject: [PATCH 22/48] Implemented RsPrettyJSON with test --- src/main/java/org/takes/rs/RsPrettyJSON.java | 129 ++++++++++++++++++ .../java/org/takes/rs/RsPrettyJSONTest.java | 107 +++++++++++++++ 2 files changed, 236 insertions(+) create mode 100644 src/main/java/org/takes/rs/RsPrettyJSON.java create mode 100644 src/test/java/org/takes/rs/RsPrettyJSONTest.java diff --git a/src/main/java/org/takes/rs/RsPrettyJSON.java b/src/main/java/org/takes/rs/RsPrettyJSON.java new file mode 100644 index 000000000..d068a4fc0 --- /dev/null +++ b/src/main/java/org/takes/rs/RsPrettyJSON.java @@ -0,0 +1,129 @@ +/** + * The MIT License (MIT) + * + * Copyright (c) 2015 Yegor Bugayenko + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package org.takes.rs; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.InputStream; +import java.util.Collections; +import java.util.List; +import java.util.Map; +import java.util.concurrent.CopyOnWriteArrayList; +import javax.json.Json; +import javax.json.JsonException; +import javax.json.JsonObject; +import javax.json.JsonReader; +import javax.json.JsonWriter; +import javax.json.JsonWriterFactory; +import javax.json.stream.JsonGenerator; +import lombok.EqualsAndHashCode; +import org.takes.Response; + +/** + * Response with properly indented JSON body. + * + *

The class is immutable and thread-safe. + * + * @author Eugene Kondrashev (eugene.kondrashev@gmail.com) + * @version $Id$ + * @since 1.0 + */ +@EqualsAndHashCode(of = "origin") +public final class RsPrettyJSON implements Response { + + /** + * Original response. + */ + private final transient Response origin; + + /** + * Response with properly transformed body. + */ + private final transient List transformed = + new CopyOnWriteArrayList(); + + /** + * Ctor. + * @param res Original response + */ + public RsPrettyJSON(final Response res) { + this.origin = res; + } + + @Override + public Iterable head() throws IOException { + return this.make().head(); + } + + @Override + public InputStream body() throws IOException { + return this.make().body(); + } + + /** + * Make a response. + * @return Response just made + * @throws java.io.IOException If fails + */ + private Response make() throws IOException { + synchronized (this.transformed) { + if (this.transformed.isEmpty()) { + this.transformed.add( + new RsWithBody( + this.origin, + RsPrettyJSON.transform(this.origin.body()) + ) + ); + } + } + return this.transformed.get(0); + } + + /** + * Format body with proper indents. + * @param body Response body + * @return New properly formatted body + * @throws java.io.IOException If fails + */ + private static byte[] transform(final InputStream body) throws IOException { + final ByteArrayOutputStream result = new ByteArrayOutputStream(); + final JsonReader reader = Json.createReader(body); + final Map props = Collections.singletonMap( + JsonGenerator.PRETTY_PRINTING, + true + ); + final JsonWriterFactory factory = Json.createWriterFactory(props); + final JsonWriter writer = factory.createWriter(result); + try { + final JsonObject obj = reader.readObject(); + writer.writeObject(obj); + } catch (final JsonException ex) { + throw new IOException(ex); + } finally { + reader.close(); + writer.close(); + } + return result.toByteArray(); + } +} diff --git a/src/test/java/org/takes/rs/RsPrettyJSONTest.java b/src/test/java/org/takes/rs/RsPrettyJSONTest.java new file mode 100644 index 000000000..eeed23d23 --- /dev/null +++ b/src/test/java/org/takes/rs/RsPrettyJSONTest.java @@ -0,0 +1,107 @@ +/** + * The MIT License (MIT) + * + * Copyright (c) 2015 Yegor Bugayenko + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included + * in all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NON-INFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ +package org.takes.rs; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import nl.jqno.equalsverifier.EqualsVerifier; +import nl.jqno.equalsverifier.Warning; +import org.hamcrest.MatcherAssert; +import org.hamcrest.Matchers; +import org.junit.Test; + +/** + * Test case for {@link org.takes.rs.RsPrettyJSON}. + * @author Eugene Kondrashev (eugene.kondrashev@gmail.com) + * @version $Id$ + * @since 1.0 + */ +public final class RsPrettyJSONTest { + + /** + * RsPrettyJSON can format response with JSON body. + * @throws java.io.IOException If some problem inside + */ + @Test + public void formatsJsonBody() throws IOException { + MatcherAssert.assertThat( + new RsPrint( + new RsPrettyJSON( + new RsWithBody("{\"widget\": {\"debug\": \"on\" }}") + ) + ).printBody(), + Matchers.is( + "\n{\n \"widget\":{\n \"debug\":\"on\"\n }\n}" + ) + ); + } + + /** + * RsPrettyJSON can format response with non JSON body. + * @throws java.io.IOException If some problem inside + */ + @Test(expected = IOException.class) + public void formatsNonJsonBody() throws IOException { + new RsPrint(new RsPrettyJSON(new RsWithBody("foo"))).printBody(); + } + + /** + * RsPrettyJSON can report correct content length. + * @throws java.io.IOException If some problem inside + */ + @Test + public void reportsCorrectContentLength() throws IOException { + final ByteArrayOutputStream baos = new ByteArrayOutputStream(); + new RsPrint( + new RsWithBody( + "\n{\n \"test\":{\n \"test\":\"test\"\n }\n}" + ) + ).printBody(baos); + MatcherAssert.assertThat( + new RsPrint( + new RsPrettyJSON( + new RsWithBody("{\"test\": {\"test\": \"test\" }}") + ) + ).printHead(), + Matchers.containsString( + String.format( + "Content-Length: %d", + baos.toByteArray().length + ) + ) + ); + } + + /** + * RsPrettyJSON can conform to equals and hash code contract. + * @throws Exception If some problem inside + */ + @Test + public void conformsToEqualsAndHashCode() throws Exception { + EqualsVerifier.forClass(RsPrettyJSON.class) + .suppress(Warning.TRANSIENT_FIELDS) + .withRedefinedSuperclass() + .verify(); + } +} From b22e34944107852985c4851a7d54877aadb3d444 Mon Sep 17 00:00:00 2001 From: Eugene Kondrashev Date: Fri, 5 Jun 2015 22:24:51 +0300 Subject: [PATCH 23/48] Moved writer.close to separate finally block avoiding redundunt close if exception was raised during read --- src/main/java/org/takes/rs/RsPrettyJSON.java | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/takes/rs/RsPrettyJSON.java b/src/main/java/org/takes/rs/RsPrettyJSON.java index d068a4fc0..f14645286 100644 --- a/src/main/java/org/takes/rs/RsPrettyJSON.java +++ b/src/main/java/org/takes/rs/RsPrettyJSON.java @@ -117,12 +117,15 @@ private static byte[] transform(final InputStream body) throws IOException { final JsonWriter writer = factory.createWriter(result); try { final JsonObject obj = reader.readObject(); - writer.writeObject(obj); + try { + writer.writeObject(obj); + } finally { + writer.close(); + } } catch (final JsonException ex) { throw new IOException(ex); } finally { reader.close(); - writer.close(); } return result.toByteArray(); } From e5ee26daa4cb542d7f8c8e39dcfebaf022a26915 Mon Sep 17 00:00:00 2001 From: Eugene Kondrashev Date: Tue, 9 Jun 2015 10:04:01 +0300 Subject: [PATCH 24/48] Review comments fixes --- src/main/java/org/takes/rs/RsPrettyJSON.java | 24 +++++++++---------- .../java/org/takes/rs/RsPrettyJSONTest.java | 16 ++++++------- 2 files changed, 19 insertions(+), 21 deletions(-) diff --git a/src/main/java/org/takes/rs/RsPrettyJSON.java b/src/main/java/org/takes/rs/RsPrettyJSON.java index f14645286..f0f0e807e 100644 --- a/src/main/java/org/takes/rs/RsPrettyJSON.java +++ b/src/main/java/org/takes/rs/RsPrettyJSON.java @@ -35,7 +35,6 @@ import javax.json.JsonObject; import javax.json.JsonReader; import javax.json.JsonWriter; -import javax.json.JsonWriterFactory; import javax.json.stream.JsonGenerator; import lombok.EqualsAndHashCode; import org.takes.Response; @@ -84,7 +83,7 @@ public InputStream body() throws IOException { /** * Make a response. * @return Response just made - * @throws java.io.IOException If fails + * @throws IOException If fails */ private Response make() throws IOException { synchronized (this.transformed) { @@ -104,29 +103,28 @@ private Response make() throws IOException { * Format body with proper indents. * @param body Response body * @return New properly formatted body - * @throws java.io.IOException If fails + * @throws IOException If fails */ private static byte[] transform(final InputStream body) throws IOException { - final ByteArrayOutputStream result = new ByteArrayOutputStream(); - final JsonReader reader = Json.createReader(body); - final Map props = Collections.singletonMap( + final ByteArrayOutputStream res = new ByteArrayOutputStream(); + final JsonReader rdr = Json.createReader(body); + final Map prp = Collections.singletonMap( JsonGenerator.PRETTY_PRINTING, true ); - final JsonWriterFactory factory = Json.createWriterFactory(props); - final JsonWriter writer = factory.createWriter(result); + final JsonWriter wrt = Json.createWriterFactory(prp).createWriter(res); try { - final JsonObject obj = reader.readObject(); + final JsonObject obj = rdr.readObject(); try { - writer.writeObject(obj); + wrt.writeObject(obj); } finally { - writer.close(); + wrt.close(); } } catch (final JsonException ex) { throw new IOException(ex); } finally { - reader.close(); + rdr.close(); } - return result.toByteArray(); + return res.toByteArray(); } } diff --git a/src/test/java/org/takes/rs/RsPrettyJSONTest.java b/src/test/java/org/takes/rs/RsPrettyJSONTest.java index eeed23d23..73e7ce4b5 100644 --- a/src/test/java/org/takes/rs/RsPrettyJSONTest.java +++ b/src/test/java/org/takes/rs/RsPrettyJSONTest.java @@ -32,7 +32,7 @@ import org.junit.Test; /** - * Test case for {@link org.takes.rs.RsPrettyJSON}. + * Test case for {@link RsPrettyJSON}. * @author Eugene Kondrashev (eugene.kondrashev@gmail.com) * @version $Id$ * @since 1.0 @@ -41,10 +41,10 @@ public final class RsPrettyJSONTest { /** * RsPrettyJSON can format response with JSON body. - * @throws java.io.IOException If some problem inside + * @throws Exception If some problem inside */ @Test - public void formatsJsonBody() throws IOException { + public void formatsJsonBody() throws Exception { MatcherAssert.assertThat( new RsPrint( new RsPrettyJSON( @@ -58,20 +58,20 @@ public void formatsJsonBody() throws IOException { } /** - * RsPrettyJSON can format response with non JSON body. - * @throws java.io.IOException If some problem inside + * RsPrettyJSON can reject a non-JSON body. + * @throws Exception If some problem inside */ @Test(expected = IOException.class) - public void formatsNonJsonBody() throws IOException { + public void rejectsNonJsonBody() throws Exception { new RsPrint(new RsPrettyJSON(new RsWithBody("foo"))).printBody(); } /** * RsPrettyJSON can report correct content length. - * @throws java.io.IOException If some problem inside + * @throws Exception If some problem inside */ @Test - public void reportsCorrectContentLength() throws IOException { + public void reportsCorrectContentLength() throws Exception { final ByteArrayOutputStream baos = new ByteArrayOutputStream(); new RsPrint( new RsWithBody( From 012a3f08e2593d1409c93608e2b4a4ef97cc6c2f Mon Sep 17 00:00:00 2001 From: ikhvostenkov Date: Tue, 9 Jun 2015 15:02:37 +0200 Subject: [PATCH 25/48] Estimate project size #149 - initial commit --- est/takes_09_06_2015.est | 17 +++++++++++++++++ 1 file changed, 17 insertions(+) create mode 100644 est/takes_09_06_2015.est diff --git a/est/takes_09_06_2015.est b/est/takes_09_06_2015.est new file mode 100644 index 000000000..57e51094b --- /dev/null +++ b/est/takes_09_06_2015.est @@ -0,0 +1,17 @@ +date: 09-06-2015 +author: Igor Khvostenkov +method: champions.pert +scope: + 1: Design of the interfaces + 2: Routing/Dispatching + 3: Persistent entities + 4: Performance testing +champions: + 1: + worst-case: 60 + best-case: 25 + most-likely: 35 + 2: + worst-case: 40 + best-case: 15 + most-likely: 25 \ No newline at end of file From ec3a3ca0f8bd159715ebb6c1b9c4d50aca072e17 Mon Sep 17 00:00:00 2001 From: Adam Siemion Date: Tue, 9 Jun 2015 16:43:35 +0200 Subject: [PATCH 26/48] #230 code review fixes --- .../takes/facets/auth/social/PsTwitter.java | 49 +++++++++---------- .../facets/auth/social/PsTwitterTest.java | 1 + 2 files changed, 24 insertions(+), 26 deletions(-) diff --git a/src/main/java/org/takes/facets/auth/social/PsTwitter.java b/src/main/java/org/takes/facets/auth/social/PsTwitter.java index 58f6802ff..3d4c58b92 100644 --- a/src/main/java/org/takes/facets/auth/social/PsTwitter.java +++ b/src/main/java/org/takes/facets/auth/social/PsTwitter.java @@ -71,9 +71,9 @@ public final class PsTwitter implements Pass { private final transient String key; /** - * Request for fetching app trequest. + * Request for fetching app token. */ - private final transient com.jcabi.http.Request trequest; + private final transient com.jcabi.http.Request tkn; /** * Request for verifying user credentials. @@ -88,38 +88,36 @@ public final class PsTwitter implements Pass { public PsTwitter(final String name, final String keys) { this( new JdkRequest( - new Href("https://api.twitter.com/oauth2/trequest") + new Href("https://api.twitter.com/oauth2/token") .with("grant_type", "client_credentials") .toString() ), - new JdkRequest(PsTwitter.VERIFY_URL), - name, - keys + new JdkRequest(PsTwitter.VERIFY_URL), name, keys ); } /** * Ctor with proper requestor for testing purposes. - * @param ttrequest HTTP request for getting trequest - * @param tvcrequest HTTP request for verifying credentials - * @param tapp Facebook app - * @param tkey Facebook key + * @param token HTTP request for getting token + * @param creds HTTP request for verifying credentials + * @param name Facebook app + * @param keys Facebook key * @checkstyle ParameterNumberCheck (3 lines) */ - PsTwitter(final com.jcabi.http.Request ttrequest, - final com.jcabi.http.Request tvcrequest, - final String tapp, - final String tkey) { - this.trequest = ttrequest; - this.user = tvcrequest; - this.app = tapp; - this.key = tkey; + PsTwitter(final com.jcabi.http.Request token, + final com.jcabi.http.Request creds, + final String name, + final String keys) { + this.tkn = token; + this.user = creds; + this.app = name; + this.key = keys; } @Override public Opt enter(final Request request) throws IOException { - return new Opt.Single(this.fetch(this.token())); + return new Opt.Single(this.fetchToken(this.token())); } @Override @@ -128,12 +126,12 @@ public Response exit(final Response response, final Identity identity) { } /** - * Get user name from Twitter, with the trequest provided. - * @param token Twitter access trequest + * Get user name from Twitter, with the token provided. + * @param token Twitter access token * @return The user found in Twitter * @throws IOException If fails */ - private Identity fetch(final String token) throws IOException { + private Identity fetchToken(final String token) throws IOException { return parse( this.user .uri() @@ -171,12 +169,12 @@ private static Identity parse(final JsonObject json) { } /** - * Retrieve Twitter access trequest. - * @return The trequest + * Retrieve Twitter access token. + * @return The Twitter access token * @throws IOException If failed */ private String token() throws IOException { - return this.trequest + return this.tkn .method("POST") .header( "Content-Type", @@ -195,5 +193,4 @@ private String token() throws IOException { .as(JsonResponse.class) .json().readObject().getString("access_token"); } - } diff --git a/src/test/java/org/takes/facets/auth/social/PsTwitterTest.java b/src/test/java/org/takes/facets/auth/social/PsTwitterTest.java index 608324eb4..729587cb6 100644 --- a/src/test/java/org/takes/facets/auth/social/PsTwitterTest.java +++ b/src/test/java/org/takes/facets/auth/social/PsTwitterTest.java @@ -41,6 +41,7 @@ /** * Test case for {@link PsTwitter}. * @author Prasath Premkumar (popprem@gmail.com) + * @author Adam Siemion (adam.siemion@lemonsoftware.pl) * @version $Id$ * @since 1.0 * @checkstyle MagicNumberCheck (500 lines) From 6936dbabfdcb0f72b7533c13e7bc459464115532 Mon Sep 17 00:00:00 2001 From: Eugene Kondrashev Date: Tue, 9 Jun 2015 21:34:42 +0300 Subject: [PATCH 27/48] Moved writer creation to the first try block in order to use it only if object was read successfully. Inlined props creation --- src/main/java/org/takes/rs/RsPrettyJSON.java | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/src/main/java/org/takes/rs/RsPrettyJSON.java b/src/main/java/org/takes/rs/RsPrettyJSON.java index f0f0e807e..e7cab776a 100644 --- a/src/main/java/org/takes/rs/RsPrettyJSON.java +++ b/src/main/java/org/takes/rs/RsPrettyJSON.java @@ -28,7 +28,6 @@ import java.io.InputStream; import java.util.Collections; import java.util.List; -import java.util.Map; import java.util.concurrent.CopyOnWriteArrayList; import javax.json.Json; import javax.json.JsonException; @@ -108,13 +107,14 @@ private Response make() throws IOException { private static byte[] transform(final InputStream body) throws IOException { final ByteArrayOutputStream res = new ByteArrayOutputStream(); final JsonReader rdr = Json.createReader(body); - final Map prp = Collections.singletonMap( - JsonGenerator.PRETTY_PRINTING, - true - ); - final JsonWriter wrt = Json.createWriterFactory(prp).createWriter(res); try { final JsonObject obj = rdr.readObject(); + final JsonWriter wrt = Json.createWriterFactory( + Collections.singletonMap( + JsonGenerator.PRETTY_PRINTING, + true + ) + ).createWriter(res); try { wrt.writeObject(obj); } finally { From c9cec80df7f88768dc8b74cdb5549c0aec3f3498 Mon Sep 17 00:00:00 2001 From: davvd Date: Tue, 9 Jun 2015 12:32:59 -0700 Subject: [PATCH 28/48] updated merge/commanders list --- .rultor.yml | 1 - 1 file changed, 1 deletion(-) diff --git a/.rultor.yml b/.rultor.yml index 0d945c069..852e52641 100644 --- a/.rultor.yml +++ b/.rultor.yml @@ -42,7 +42,6 @@ merge: - krzyk - longtimeago - pinaf - - yegor256 deploy: script: | version=$(curl -K ../curl-appveyor.cfg --data "{accountName: 'yegor256', projectSlug: 'takes', branch: 'master'}" https://ci.appveyor.com/api/builds | jq -r '.version') From 6b90f56d0df5c275e7992268d047dc67ca7f3eea Mon Sep 17 00:00:00 2001 From: Endrigo Antonini Date: Tue, 9 Jun 2015 21:44:55 -0300 Subject: [PATCH 29/48] #221 Implemented http 401 status and header when not authenticated. Also renamed method from check to enter. --- .../java/org/takes/facets/auth/PsBasic.java | 40 +++++++++++++++---- .../org/takes/facets/auth/PsBasicTest.java | 30 +++++++++++--- 2 files changed, 58 insertions(+), 12 deletions(-) diff --git a/src/main/java/org/takes/facets/auth/PsBasic.java b/src/main/java/org/takes/facets/auth/PsBasic.java index 49821dca9..f1ba0c4ef 100644 --- a/src/main/java/org/takes/facets/auth/PsBasic.java +++ b/src/main/java/org/takes/facets/auth/PsBasic.java @@ -24,12 +24,17 @@ package org.takes.facets.auth; import java.io.IOException; +import java.net.HttpURLConnection; +import java.util.logging.Level; import javax.xml.bind.DatatypeConverter; import lombok.EqualsAndHashCode; import org.takes.Request; import org.takes.Response; +import org.takes.facets.flash.RsFlash; +import org.takes.facets.forward.RsForward; import org.takes.misc.Opt; import org.takes.rq.RqHeaders; +import org.takes.rs.RsWithHeaders; /** * Pass that checks the user according RFC-2617. @@ -39,8 +44,9 @@ * @author Endrigo Antonini (teamed@endrigo.com.br) * @version $Id$ * @since 0.20 + * @checkstyle ClassDataAbstractionCouplingCheck (250 lines) */ -@EqualsAndHashCode(of = { "entry" }) +@EqualsAndHashCode(of = { "entry", "realm" }) public final class PsBasic implements Pass { /** @@ -53,11 +59,18 @@ public final class PsBasic implements Pass { */ private final transient PsBasic.Entry entry; + /** + * Realm. + */ + private final transient String realm; + /** * Ctor. + * @param rlm Realm * @param basic Entry */ - public PsBasic(final PsBasic.Entry basic) { + public PsBasic(final String rlm, final PsBasic.Entry basic) { + this.realm = rlm; this.entry = basic; } @@ -73,7 +86,7 @@ public Opt enter(final Request request) throws IOException { ) ).trim(); final String user = decoded.split(":")[0]; - if (this.entry.check( + if (this.entry.enter( user, decoded.substring(user.length() + 1) ).has()) { @@ -86,6 +99,19 @@ public Opt enter(final Request request) throws IOException { } catch (final IOException ex) { identity = new Opt.Empty(); } + if (!identity.has()) { + throw new RsForward( + new RsWithHeaders( + new RsFlash("access denied", Level.WARNING), + String.format( + "WWW-Authenticate: Basic ream=\"%s\"", + this.realm + ) + ), + HttpURLConnection.HTTP_UNAUTHORIZED, + "/login/start" + ); + } return identity; } @@ -111,7 +137,7 @@ public interface Entry { * @param pwd Password * @return Identity. */ - Opt check(String user, String pwd); + Opt enter(String user, String pwd); } /** @@ -145,11 +171,11 @@ public Fixed(final String user, final String pwd) { } @Override - public Opt check(final String user, final String pwd) { + public Opt enter(final String user, final String pwd) { final Opt identity; if (this.username.equals(user) && this.password.equals(pwd)) { identity = new Opt.Single( - new Identity.Simple(user) + new Identity.Simple(user) ); } else { identity = new Opt.Empty(); @@ -168,7 +194,7 @@ public Opt check(final String user, final String pwd) { public static final class Empty implements PsBasic.Entry { @Override - public Opt check(final String user, final String pwd) { + public Opt enter(final String user, final String pwd) { return new Opt.Empty(); } } diff --git a/src/test/java/org/takes/facets/auth/PsBasicTest.java b/src/test/java/org/takes/facets/auth/PsBasicTest.java index 3df7593dc..1e1ff4567 100644 --- a/src/test/java/org/takes/facets/auth/PsBasicTest.java +++ b/src/test/java/org/takes/facets/auth/PsBasicTest.java @@ -29,10 +29,12 @@ import org.hamcrest.MatcherAssert; import org.hamcrest.Matchers; import org.junit.Test; +import org.takes.facets.forward.RsForward; import org.takes.misc.Opt; import org.takes.rq.RqFake; import org.takes.rq.RqMethod; import org.takes.rq.RqWithHeaders; +import org.takes.rs.RsPrint; /** * Test of {@link PsBasic}. @@ -67,6 +69,7 @@ public void handleConnectionWithValidCredential() throws Exception { this.generateAuthenticateHead(user, pass) ); final Opt identity = new PsBasic( + "RealmA", new PsBasic.Fixed(user, pass) ).enter(req); MatcherAssert.assertThat(identity.has(), Matchers.is(true)); @@ -81,7 +84,9 @@ public void handleConnectionWithValidCredential() throws Exception { * @throws Exception If some problem inside */ @Test +// @Test(expected = RsForward.class) public void handleConnectionWithInvalidCredential() throws Exception { + RsForward forward = new RsForward(); final RqWithHeaders req = new RqWithHeaders( new RqFake( RqMethod.GET, @@ -93,10 +98,23 @@ public void handleConnectionWithInvalidCredential() throws Exception { ), this.generateAuthenticateHead("username", "wrong") ); - final Opt identity = new PsBasic( - new PsBasic.Empty() - ).enter(req); - MatcherAssert.assertThat(identity.has(), Matchers.is(false)); + try { + new PsBasic( + "RealmB", + new PsBasic.Empty() + ).enter(req); + } catch (final RsForward ex) { + forward = ex; + } + MatcherAssert.assertThat( + new RsPrint(forward).printHead(), + Matchers.allOf( + Matchers.containsString("HTTP/1.1 401 Unauthorized"), + Matchers.containsString( + "WWW-Authenticate: Basic ream=\"RealmB\"" + ) + ) + ); } /** @@ -124,6 +142,7 @@ public void handleMultipleHeadersWithValidCredential() throws Exception { "X-Powered-By:Java/1.7" ); final Opt identity = new PsBasic( + "RealmC", new PsBasic.Fixed(user, pass) ).enter(req); MatcherAssert.assertThat(identity.has(), Matchers.is(true)); @@ -137,7 +156,7 @@ public void handleMultipleHeadersWithValidCredential() throws Exception { * PsBasic can handle multiple headers with invalid content. * @throws Exception If some problem inside */ - @Test + @Test(expected = RsForward.class) public void handleMultipleHeadersWithInvalidContent() throws Exception { final String user = "user"; final String pass = "password"; @@ -154,6 +173,7 @@ public void handleMultipleHeadersWithInvalidContent() throws Exception { "XYZX-Powered-By:Java/1.7" ); final Opt identity = new PsBasic( + "RealmD", new PsBasic.Fixed(user, pass) ).enter(req); MatcherAssert.assertThat(identity.has(), Matchers.is(false)); From 071195932ad3d6be68a671938cc483b36f8807cb Mon Sep 17 00:00:00 2001 From: Lautaro Cozzani Date: Tue, 9 Jun 2015 22:13:13 -0300 Subject: [PATCH 30/48] #263: Map cache with lazy loading --- src/main/java/org/takes/rq/RqForm.java | 86 ++++++++++++++++---------- 1 file changed, 53 insertions(+), 33 deletions(-) diff --git a/src/main/java/org/takes/rq/RqForm.java b/src/main/java/org/takes/rq/RqForm.java index 2efc7aed7..eb2f0f7d4 100644 --- a/src/main/java/org/takes/rq/RqForm.java +++ b/src/main/java/org/takes/rq/RqForm.java @@ -23,7 +23,6 @@ */ package org.takes.rq; -import com.jcabi.aspects.Cacheable; import java.io.ByteArrayOutputStream; import java.io.IOException; import java.io.InputStream; @@ -38,6 +37,7 @@ import java.util.Locale; import java.util.concurrent.ConcurrentHashMap; import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.CopyOnWriteArrayList; import lombok.EqualsAndHashCode; import org.takes.HttpException; import org.takes.Request; @@ -69,14 +69,16 @@ public interface RqForm extends Request { * Get single parameter. * @param name Parameter name * @return List of values (can be empty) + * @throws IOException if something fails reading parameters */ - Iterable param(CharSequence name); + Iterable param(CharSequence name) throws IOException; /** * Get all parameter names. * @return All names + * @throws IOException if something fails reading parameters */ - Iterable names(); + Iterable names() throws IOException; /** * Base implementation of @link RqForm. @@ -91,6 +93,13 @@ final class Base extends RqWrap implements RqForm { */ private final transient Request req; + /** + * Saved map. + * @checkstyle LineLengthCheck (3 lines) + */ + private final transient List>> saved = + new CopyOnWriteArrayList>>(); + /** * Ctor. * @param request Original request @@ -102,7 +111,9 @@ public Base(final Request request) throws IOException { this.req = request; } @Override - public Iterable param(final CharSequence key) { + public Iterable param( + final CharSequence key + ) throws IOException { final List values = this.map().get(key.toString().toLowerCase(Locale.ENGLISH)); final Iterable iter; @@ -126,7 +137,7 @@ public Iterable param(final CharSequence key) { return iter; } @Override - public Iterable names() { + public Iterable names() throws IOException { return this.map().keySet(); } /** @@ -146,35 +157,39 @@ private static String decode(final CharSequence txt) { /** * Create map of request parameter. * @return Parameters map or empty map in case of error. + * @throws IOException If something fails reading or parsing body */ - @Cacheable(forever = true) - private ConcurrentMap> map() { - try { - final ByteArrayOutputStream baos = new ByteArrayOutputStream(); - new RqPrint(this.req).printBody(baos); - final String body = new String(baos.toByteArray()); - final ConcurrentMap> map = - new ConcurrentHashMap>(0); - for (final String pair : body.split("&")) { - if (pair.isEmpty()) { - continue; - } - final String[] parts = pair.split("=", 2); - if (parts.length < 2) { - throw new HttpException( - HttpURLConnection.HTTP_BAD_REQUEST, - String.format("invalid form body pair: %s", pair) + private ConcurrentMap> map() throws IOException { + synchronized (this.saved) { + if (this.saved.isEmpty()) { + final ByteArrayOutputStream + baos = new ByteArrayOutputStream(); + new RqPrint(this.req).printBody(baos); + final String body = new String(baos.toByteArray()); + final ConcurrentMap> map = + new ConcurrentHashMap>(0); + for (final String pair : body.split("&")) { + if (pair.isEmpty()) { + continue; + } + final String[] parts = pair.split("=", 2); + if (parts.length < 2) { + throw new HttpException( + HttpURLConnection.HTTP_BAD_REQUEST, + String.format( + "invalid form body pair: %s", pair + ) + ); + } + final String key = RqForm.Base.decode( + parts[0].trim().toLowerCase(Locale.ENGLISH) ); + map.putIfAbsent(key, new LinkedList()); + map.get(key).add(RqForm.Base.decode(parts[1].trim())); } - final String key = RqForm.Base.decode( - parts[0].trim().toLowerCase(Locale.ENGLISH) - ); - map.putIfAbsent(key, new LinkedList()); - map.get(key).add(RqForm.Base.decode(parts[1].trim())); + this.saved.add(map); } - return map; - } catch (final IOException ex) { - return new ConcurrentHashMap>(0); + return this.saved.get(0); } } } @@ -200,11 +215,13 @@ public Smart(final RqForm req) { this.origin = req; } @Override - public Iterable param(final CharSequence name) { + public Iterable param( + final CharSequence name + ) throws IOException { return this.origin.param(name); } @Override - public Iterable names() { + public Iterable names() throws IOException { return this.origin.names(); } @Override @@ -238,8 +255,11 @@ public String single(final CharSequence name) throws IOException { * @param name Name of query param * @param def Default, if not found * @return Value of it + * @throws IOException if something fails reading parameters */ - public String single(final CharSequence name, final String def) { + public String single( + final CharSequence name, final String def + ) throws IOException { final String value; final Iterator params = this.param(name).iterator(); if (params.hasNext()) { From 9a8d16fbe28e0933d447b3716ad98cdd10f0eead Mon Sep 17 00:00:00 2001 From: Adam Siemion Date: Wed, 10 Jun 2015 12:10:26 +0200 Subject: [PATCH 31/48] #230 field tkn -> token; method params token -> tkn; method fetchToken -> identity; method token -> fetch --- .../takes/facets/auth/social/PsTwitter.java | 20 +++++++++---------- 1 file changed, 10 insertions(+), 10 deletions(-) diff --git a/src/main/java/org/takes/facets/auth/social/PsTwitter.java b/src/main/java/org/takes/facets/auth/social/PsTwitter.java index 3d4c58b92..5893f7a5f 100644 --- a/src/main/java/org/takes/facets/auth/social/PsTwitter.java +++ b/src/main/java/org/takes/facets/auth/social/PsTwitter.java @@ -73,7 +73,7 @@ public final class PsTwitter implements Pass { /** * Request for fetching app token. */ - private final transient com.jcabi.http.Request tkn; + private final transient com.jcabi.http.Request token; /** * Request for verifying user credentials. @@ -98,17 +98,17 @@ public PsTwitter(final String name, final String keys) { /** * Ctor with proper requestor for testing purposes. - * @param token HTTP request for getting token + * @param tkn HTTP request for getting token * @param creds HTTP request for verifying credentials * @param name Facebook app * @param keys Facebook key * @checkstyle ParameterNumberCheck (3 lines) */ - PsTwitter(final com.jcabi.http.Request token, + PsTwitter(final com.jcabi.http.Request tkn, final com.jcabi.http.Request creds, final String name, final String keys) { - this.tkn = token; + this.token = tkn; this.user = creds; this.app = name; this.key = keys; @@ -117,7 +117,7 @@ public PsTwitter(final String name, final String keys) { @Override public Opt enter(final Request request) throws IOException { - return new Opt.Single(this.fetchToken(this.token())); + return new Opt.Single(this.identity(this.fetch())); } @Override @@ -127,18 +127,18 @@ public Response exit(final Response response, final Identity identity) { /** * Get user name from Twitter, with the token provided. - * @param token Twitter access token + * @param tkn Twitter access token * @return The user found in Twitter * @throws IOException If fails */ - private Identity fetchToken(final String token) throws IOException { + private Identity identity(final String tkn) throws IOException { return parse( this.user .uri() .set( URI.create( new Href(PsTwitter.VERIFY_URL) - .with("access_token", token) + .with("access_token", tkn) .toString() ) ) @@ -173,8 +173,8 @@ private static Identity parse(final JsonObject json) { * @return The Twitter access token * @throws IOException If failed */ - private String token() throws IOException { - return this.tkn + private String fetch() throws IOException { + return this.token .method("POST") .header( "Content-Type", From 15246381009a968d5ac52b2ce96f2d24bdceca83 Mon Sep 17 00:00:00 2001 From: Endrigo Antonini Date: Wed, 10 Jun 2015 22:40:04 -0300 Subject: [PATCH 32/48] #221 Removed code complexity. --- .../java/org/takes/facets/auth/PsBasic.java | 39 +++++++------------ .../org/takes/facets/auth/PsBasicTest.java | 4 +- 2 files changed, 17 insertions(+), 26 deletions(-) diff --git a/src/main/java/org/takes/facets/auth/PsBasic.java b/src/main/java/org/takes/facets/auth/PsBasic.java index f1ba0c4ef..b3b2d5747 100644 --- a/src/main/java/org/takes/facets/auth/PsBasic.java +++ b/src/main/java/org/takes/facets/auth/PsBasic.java @@ -76,29 +76,18 @@ public PsBasic(final String rlm, final PsBasic.Entry basic) { @Override public Opt enter(final Request request) throws IOException { - Opt identity = new Opt.Empty(); - try { - final String decoded = new String( - DatatypeConverter.parseBase64Binary( - new RqHeaders.Smart( - new RqHeaders.Base(request) - ).single("authorization").split(AUTH_HEAD)[1] - ) - ).trim(); - final String user = decoded.split(":")[0]; - if (this.entry.enter( - user, - decoded.substring(user.length() + 1) - ).has()) { - identity = new Opt.Single( - new Identity.Simple( - String.format("urn:basic:%s", user) - ) - ); - } - } catch (final IOException ex) { - identity = new Opt.Empty(); - } + final String decoded = new String( + DatatypeConverter.parseBase64Binary( + new RqHeaders.Smart( + new RqHeaders.Base(request) + ).single("authorization").split(AUTH_HEAD)[1] + ) + ).trim(); + final String user = decoded.split(":")[0]; + final Opt identity = this.entry.enter( + user, + decoded.substring(user.length() + 1) + ); if (!identity.has()) { throw new RsForward( new RsWithHeaders( @@ -175,7 +164,9 @@ public Opt enter(final String user, final String pwd) { final Opt identity; if (this.username.equals(user) && this.password.equals(pwd)) { identity = new Opt.Single( - new Identity.Simple(user) + new Identity.Simple( + String.format("urn:basic:%s", user) + ) ); } else { identity = new Opt.Empty(); diff --git a/src/test/java/org/takes/facets/auth/PsBasicTest.java b/src/test/java/org/takes/facets/auth/PsBasicTest.java index 1e1ff4567..d223ff819 100644 --- a/src/test/java/org/takes/facets/auth/PsBasicTest.java +++ b/src/test/java/org/takes/facets/auth/PsBasicTest.java @@ -29,6 +29,7 @@ import org.hamcrest.MatcherAssert; import org.hamcrest.Matchers; import org.junit.Test; +import org.takes.HttpException; import org.takes.facets.forward.RsForward; import org.takes.misc.Opt; import org.takes.rq.RqFake; @@ -84,7 +85,6 @@ public void handleConnectionWithValidCredential() throws Exception { * @throws Exception If some problem inside */ @Test -// @Test(expected = RsForward.class) public void handleConnectionWithInvalidCredential() throws Exception { RsForward forward = new RsForward(); final RqWithHeaders req = new RqWithHeaders( @@ -156,7 +156,7 @@ public void handleMultipleHeadersWithValidCredential() throws Exception { * PsBasic can handle multiple headers with invalid content. * @throws Exception If some problem inside */ - @Test(expected = RsForward.class) + @Test(expected = HttpException.class) public void handleMultipleHeadersWithInvalidContent() throws Exception { final String user = "user"; final String pass = "password"; From 4575cc25c8371e2ac45edebc6f4cc1d1c1f664dd Mon Sep 17 00:00:00 2001 From: Endrigo Antonini Date: Fri, 12 Jun 2015 09:06:18 -0300 Subject: [PATCH 33/48] #221 Changed redirect url. --- src/main/java/org/takes/facets/auth/PsBasic.java | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/takes/facets/auth/PsBasic.java b/src/main/java/org/takes/facets/auth/PsBasic.java index b3b2d5747..f941f8a96 100644 --- a/src/main/java/org/takes/facets/auth/PsBasic.java +++ b/src/main/java/org/takes/facets/auth/PsBasic.java @@ -34,6 +34,7 @@ import org.takes.facets.forward.RsForward; import org.takes.misc.Opt; import org.takes.rq.RqHeaders; +import org.takes.rq.RqHref; import org.takes.rs.RsWithHeaders; /** @@ -98,7 +99,7 @@ public Opt enter(final Request request) throws IOException { ) ), HttpURLConnection.HTTP_UNAUTHORIZED, - "/login/start" + new RqHref.Base(request).href() ); } return identity; From 44b5683578753378096fc8d9f5c6a33569fb9b2e Mon Sep 17 00:00:00 2001 From: Lautaro Cozzani Date: Fri, 12 Jun 2015 13:44:04 -0300 Subject: [PATCH 34/48] #263: fix feedbacks --- src/main/java/org/takes/rq/RqForm.java | 13 +++++-------- 1 file changed, 5 insertions(+), 8 deletions(-) diff --git a/src/main/java/org/takes/rq/RqForm.java b/src/main/java/org/takes/rq/RqForm.java index eb2f0f7d4..170936f06 100644 --- a/src/main/java/org/takes/rq/RqForm.java +++ b/src/main/java/org/takes/rq/RqForm.java @@ -105,7 +105,6 @@ final class Base extends RqWrap implements RqForm { * @param request Original request * @throws IOException If fails */ - @SuppressWarnings("PMD.AvoidInstantiatingObjectsInLoops") public Base(final Request request) throws IOException { super(request); this.req = request; @@ -167,7 +166,7 @@ private ConcurrentMap> map() throws IOException { new RqPrint(this.req).printBody(baos); final String body = new String(baos.toByteArray()); final ConcurrentMap> map = - new ConcurrentHashMap>(0); + new ConcurrentHashMap>(1); for (final String pair : body.split("&")) { if (pair.isEmpty()) { continue; @@ -215,9 +214,8 @@ public Smart(final RqForm req) { this.origin = req; } @Override - public Iterable param( - final CharSequence name - ) throws IOException { + public Iterable param(final CharSequence name) + throws IOException { return this.origin.param(name); } @Override @@ -257,9 +255,8 @@ public String single(final CharSequence name) throws IOException { * @return Value of it * @throws IOException if something fails reading parameters */ - public String single( - final CharSequence name, final String def - ) throws IOException { + public String single(final CharSequence name, final String def) + throws IOException { final String value; final Iterator params = this.param(name).iterator(); if (params.hasNext()) { From c60a856f4f6a4f3bf782871bd9784ec8e99b9af7 Mon Sep 17 00:00:00 2001 From: Endrigo Antonini Date: Fri, 12 Jun 2015 20:22:08 -0300 Subject: [PATCH 35/48] #221 Changed to use RsWithHeaders instead RsWithHeader. --- src/main/java/org/takes/facets/auth/PsBasic.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/main/java/org/takes/facets/auth/PsBasic.java b/src/main/java/org/takes/facets/auth/PsBasic.java index f941f8a96..ffe8fafa3 100644 --- a/src/main/java/org/takes/facets/auth/PsBasic.java +++ b/src/main/java/org/takes/facets/auth/PsBasic.java @@ -35,7 +35,7 @@ import org.takes.misc.Opt; import org.takes.rq.RqHeaders; import org.takes.rq.RqHref; -import org.takes.rs.RsWithHeaders; +import org.takes.rs.RsWithHeader; /** * Pass that checks the user according RFC-2617. @@ -91,7 +91,7 @@ public Opt enter(final Request request) throws IOException { ); if (!identity.has()) { throw new RsForward( - new RsWithHeaders( + new RsWithHeader( new RsFlash("access denied", Level.WARNING), String.format( "WWW-Authenticate: Basic ream=\"%s\"", From d1a0f1d0daaccbe1cc20f785b1c47116eaa0374f Mon Sep 17 00:00:00 2001 From: Endrigo Antonini Date: Fri, 12 Jun 2015 20:51:11 -0300 Subject: [PATCH 36/48] #221 Renamed to also improved unit test to avoid storing the content on variables. --- .../java/org/takes/facets/auth/PsBasic.java | 37 +++-- .../org/takes/facets/auth/PsBasicTest.java | 131 +++++++++--------- 2 files changed, 84 insertions(+), 84 deletions(-) diff --git a/src/main/java/org/takes/facets/auth/PsBasic.java b/src/main/java/org/takes/facets/auth/PsBasic.java index ffe8fafa3..a8575ad6c 100644 --- a/src/main/java/org/takes/facets/auth/PsBasic.java +++ b/src/main/java/org/takes/facets/auth/PsBasic.java @@ -131,48 +131,43 @@ public interface Entry { } /** - * Entry with fixed credentials. + * Fake implementation of {@link PsBasic.Entry}. + * + *

The class is immutable and thread-safe. * * @author Endrigo Antonini (teamed@endrigo.com.br) * @version $Id$ * @since 0.20 * */ - public static final class Fixed implements PsBasic.Entry { + public static final class Fake implements PsBasic.Entry { /** - * Username. + * Should we authenticate a user? */ - private final transient String username; - - /** - * Password. - */ - private final transient String password; + private final transient boolean condition; /** * Ctor. - * @param user Username. - * @param pwd Password. + * @param cond Condition */ - public Fixed(final String user, final String pwd) { - this.username = user; - this.password = pwd; + public Fake(final boolean cond) { + this.condition = cond; } @Override - public Opt enter(final String user, final String pwd) { - final Opt identity; - if (this.username.equals(user) && this.password.equals(pwd)) { - identity = new Opt.Single( + public Opt enter(final String usr, final String pwd) { + final Opt user; + if (this.condition) { + user = new Opt.Single( new Identity.Simple( - String.format("urn:basic:%s", user) + String.format("urn:basic:%s", usr) ) ); } else { - identity = new Opt.Empty(); + user = new Opt.Empty(); } - return identity; + return user; } } diff --git a/src/test/java/org/takes/facets/auth/PsBasicTest.java b/src/test/java/org/takes/facets/auth/PsBasicTest.java index d223ff819..50bc3f453 100644 --- a/src/test/java/org/takes/facets/auth/PsBasicTest.java +++ b/src/test/java/org/takes/facets/auth/PsBasicTest.java @@ -57,22 +57,22 @@ public final class PsBasicTest { @Test public void handleConnectionWithValidCredential() throws Exception { final String user = "john"; - final String pass = "pass"; - final RqWithHeaders req = new RqWithHeaders( - new RqFake( - RqMethod.GET, - String.format( - "?valid_code=%s", - // @checkstyle MagicNumberCheck (1 line) - RandomStringUtils.randomAlphanumeric(10) - ) - ), - this.generateAuthenticateHead(user, pass) - ); final Opt identity = new PsBasic( "RealmA", - new PsBasic.Fixed(user, pass) - ).enter(req); + new PsBasic.Fake(true) + ).enter( + new RqWithHeaders( + new RqFake( + RqMethod.GET, + String.format( + "?valid_code=%s", + // @checkstyle MagicNumberCheck (1 line) + RandomStringUtils.randomAlphanumeric(10) + ) + ), + this.generateAuthenticateHead(user, "pass") + ) + ); MatcherAssert.assertThat(identity.has(), Matchers.is(true)); MatcherAssert.assertThat( identity.get().urn(), @@ -87,22 +87,23 @@ public void handleConnectionWithValidCredential() throws Exception { @Test public void handleConnectionWithInvalidCredential() throws Exception { RsForward forward = new RsForward(); - final RqWithHeaders req = new RqWithHeaders( - new RqFake( - RqMethod.GET, - String.format( - "?invalid_code=%s", - // @checkstyle MagicNumberCheck (1 line) - RandomStringUtils.randomAlphanumeric(10) - ) - ), - this.generateAuthenticateHead("username", "wrong") - ); try { new PsBasic( "RealmB", new PsBasic.Empty() - ).enter(req); + ).enter( + new RqWithHeaders( + new RqFake( + RqMethod.GET, + String.format( + "?invalid_code=%s", + // @checkstyle MagicNumberCheck (1 line) + RandomStringUtils.randomAlphanumeric(10) + ) + ), + this.generateAuthenticateHead("username", "wrong") + ) + ); } catch (final RsForward ex) { forward = ex; } @@ -124,27 +125,27 @@ public void handleConnectionWithInvalidCredential() throws Exception { @Test public void handleMultipleHeadersWithValidCredential() throws Exception { final String user = "bill"; - final String pass = "changeit"; - final RqWithHeaders req = new RqWithHeaders( - new RqFake( - RqMethod.GET, - String.format( - "?multiple_code=%s", - // @checkstyle MagicNumberCheck (1 line) - RandomStringUtils.randomAlphanumeric(10) - ) - ), - this.generateAuthenticateHead(user, pass), - "Referer: http://teamed.io/", - "Connection:keep-alive", - "Content-Encoding:gzip", - "X-Check-Cacheable:YES", - "X-Powered-By:Java/1.7" - ); final Opt identity = new PsBasic( "RealmC", - new PsBasic.Fixed(user, pass) - ).enter(req); + new PsBasic.Fake(true) + ).enter( + new RqWithHeaders( + new RqFake( + RqMethod.GET, + String.format( + "?multiple_code=%s", + // @checkstyle MagicNumberCheck (1 line) + RandomStringUtils.randomAlphanumeric(10) + ) + ), + this.generateAuthenticateHead(user, "changeit"), + "Referer: http://teamed.io/", + "Connection:keep-alive", + "Content-Encoding:gzip", + "X-Check-Cacheable:YES", + "X-Powered-By:Java/1.7" + ) + ); MatcherAssert.assertThat(identity.has(), Matchers.is(true)); MatcherAssert.assertThat( identity.get().urn(), @@ -158,25 +159,29 @@ public void handleMultipleHeadersWithValidCredential() throws Exception { */ @Test(expected = HttpException.class) public void handleMultipleHeadersWithInvalidContent() throws Exception { - final String user = "user"; - final String pass = "password"; - final RqWithHeaders req = new RqWithHeaders( - new RqFake( - "XPTO", - "/wrong-url" - ), - String.format("XYZ%s", this.generateAuthenticateHead(user, pass)), - "XYZReferer: http://teamed.io/", - "XYZConnection:keep-alive", - "XYZContent-Encoding:gzip", - "XYZX-Check-Cacheable:YES", - "XYZX-Powered-By:Java/1.7" + MatcherAssert.assertThat( + new PsBasic( + "RealmD", + new PsBasic.Fake(true) + ).enter( + new RqWithHeaders( + new RqFake( + "XPTO", + "/wrong-url" + ), + String.format( + "XYZ%s", + this.generateAuthenticateHead("user", "password") + ), + "XYZReferer: http://teamed.io/", + "XYZConnection:keep-alive", + "XYZContent-Encoding:gzip", + "XYZX-Check-Cacheable:YES", + "XYZX-Powered-By:Java/1.7" + ) + ).has() + , Matchers.is(false) ); - final Opt identity = new PsBasic( - "RealmD", - new PsBasic.Fixed(user, pass) - ).enter(req); - MatcherAssert.assertThat(identity.has(), Matchers.is(false)); } /** From 53ef4da48ccb6c634fef62e41a32ee5a9278295e Mon Sep 17 00:00:00 2001 From: Lautaro Cozzani Date: Sat, 13 Jun 2015 13:44:03 -0300 Subject: [PATCH 37/48] #263: add test for cache --- src/test/java/org/takes/rq/RqFormTest.java | 22 ++++++++++++++++++++++ 1 file changed, 22 insertions(+) diff --git a/src/test/java/org/takes/rq/RqFormTest.java b/src/test/java/org/takes/rq/RqFormTest.java index 5947f4b09..fe7fd67a4 100644 --- a/src/test/java/org/takes/rq/RqFormTest.java +++ b/src/test/java/org/takes/rq/RqFormTest.java @@ -64,4 +64,26 @@ public void parsesHttpBody() throws IOException { ); } + /** + * Returns always same instances (Cache). + * @throws IOException if fails + */ + @Test + public void sameInstance() throws IOException { + final RqForm req = new RqForm.Base( + new RqBuffered( + new RqFake( + Arrays.asList( + "GET /path?a=3", + "Host: www.example2.com" + ), + "alpha=a+b+c&beta=%20No%20" + ) + ) + ); + MatcherAssert.assertThat( + req.names() == req.names(), + Matchers.is(Boolean.TRUE) + ); + } } From cdf22da9a376179af1c39d51a2c6e42e44afe087 Mon Sep 17 00:00:00 2001 From: erim erturk Date: Sun, 14 Jun 2015 11:04:43 +0300 Subject: [PATCH 38/48] #342 Added single or default value method to RqHeaders --- src/main/java/org/takes/rq/RqHeaders.java | 20 ++++++++ src/main/java/org/takes/tk/TkCORS.java | 7 --- src/test/java/org/takes/rq/RqHeadersTest.java | 46 +++++++++++++++++++ 3 files changed, 66 insertions(+), 7 deletions(-) diff --git a/src/main/java/org/takes/rq/RqHeaders.java b/src/main/java/org/takes/rq/RqHeaders.java index 2e8d1f66a..f0bfb4cec 100644 --- a/src/main/java/org/takes/rq/RqHeaders.java +++ b/src/main/java/org/takes/rq/RqHeaders.java @@ -49,6 +49,7 @@ * @version $Id$ * @since 0.1 */ +@SuppressWarnings("PMD.TooManyMethods") public interface RqHeaders extends Request { /** @@ -210,5 +211,24 @@ public String single(final CharSequence name) throws IOException { } return params.next(); } + /** + * Get param or default. + * @param name Name of query param + * @param def Default, if not found + * @return Value of it + * @throws IOException If fails + */ + public String single(final CharSequence name, final CharSequence def) + throws IOException { + final String value; + final Iterator params = this.header(name).iterator(); + if (params.hasNext()) { + value = params.next(); + } else { + value = def.toString(); + } + return value; + } + } } diff --git a/src/main/java/org/takes/tk/TkCORS.java b/src/main/java/org/takes/tk/TkCORS.java index 01c410095..29856187c 100644 --- a/src/main/java/org/takes/tk/TkCORS.java +++ b/src/main/java/org/takes/tk/TkCORS.java @@ -78,13 +78,6 @@ public Response act(final Request req) throws IOException { String domain; final Response response; try { - // @checkstyle MethodBodyCommentsCheck (7 lines) - // @todo #258:30min RqHeaders.Smart should have a `single(string, - // string)` method. That method should be similar to the method - // found in `RqHref.Smart`). The first parameter is the key that - // you are looking for and the second one is the default value if - // it was not found. Don't forget to remove the checkstyle - // comment. domain = new RqHeaders.Smart( new RqHeaders.Base(req) ).single("origin"); diff --git a/src/test/java/org/takes/rq/RqHeadersTest.java b/src/test/java/org/takes/rq/RqHeadersTest.java index e76ac0a91..3d04e0eaf 100644 --- a/src/test/java/org/takes/rq/RqHeadersTest.java +++ b/src/test/java/org/takes/rq/RqHeadersTest.java @@ -80,4 +80,50 @@ public void findsAllHeaders() throws IOException { ); } + /** + * RqHeaders can find single header. + * @throws IOException If some problem inside + */ + @Test + public void findsSingleHeader() throws IOException { + MatcherAssert.assertThat( + new RqHeaders.Smart( + new RqHeaders.Base( + new RqFake( + Arrays.asList( + "GET /g", + "Host: www.takes.com" + ), + "" + ) + ) + // @checkstyle MultipleStringLiteralsCheck (1 line) + ).single("host", "www.takes.net"), + Matchers.equalTo("www.takes.com") + ); + } + /** + * RqHeaders can find default header. + * @throws IOException If some problem inside + */ + @Test + public void findsDefaultHeader() throws IOException { + final String host = "www.takes.org"; + MatcherAssert.assertThat( + new RqHeaders.Smart( + new RqHeaders.Base( + new RqFake( + Arrays.asList( + "GET /f", + "Accept: text/json" + ), + "" + ) + ) + // @checkstyle MultipleStringLiteralsCheck (1 line) + ).single("host", host), + Matchers.equalTo(host) + ); + } + } From 0d7fcd51f63f8c03a8311c64c231e475411eacb3 Mon Sep 17 00:00:00 2001 From: erim erturk Date: Sun, 14 Jun 2015 20:02:55 +0300 Subject: [PATCH 39/48] #342 replaced try catch block --- src/main/java/org/takes/tk/TkCORS.java | 11 +++-------- 1 file changed, 3 insertions(+), 8 deletions(-) diff --git a/src/main/java/org/takes/tk/TkCORS.java b/src/main/java/org/takes/tk/TkCORS.java index 29856187c..9357c24e8 100644 --- a/src/main/java/org/takes/tk/TkCORS.java +++ b/src/main/java/org/takes/tk/TkCORS.java @@ -75,15 +75,10 @@ public TkCORS(final Take take, final String... domains) { @Override public Response act(final Request req) throws IOException { - String domain; final Response response; - try { - domain = new RqHeaders.Smart( - new RqHeaders.Base(req) - ).single("origin"); - } catch (final IOException exp) { - domain = ""; - } + final String domain = new RqHeaders.Smart( + new RqHeaders.Base(req) + ).single("origin", ""); if (this.allowed.contains(domain)) { response = new RsWithHeaders( this.origin.act(req), From b7ff06602e963664c655353fec6593ab49aabee9 Mon Sep 17 00:00:00 2001 From: erim erturk Date: Mon, 15 Jun 2015 09:30:45 +0300 Subject: [PATCH 40/48] #342 refactor javadoc and method names --- src/main/java/org/takes/rq/RqHeaders.java | 11 +++++++---- src/test/java/org/takes/rq/RqHeadersTest.java | 16 +++++++--------- 2 files changed, 14 insertions(+), 13 deletions(-) diff --git a/src/main/java/org/takes/rq/RqHeaders.java b/src/main/java/org/takes/rq/RqHeaders.java index f0bfb4cec..c1f179362 100644 --- a/src/main/java/org/takes/rq/RqHeaders.java +++ b/src/main/java/org/takes/rq/RqHeaders.java @@ -48,6 +48,8 @@ * @author Yegor Bugayenko (yegor@teamed.io) * @version $Id$ * @since 0.1 + * @todo #342:30min/DEV Extract RqHeaders.Smart into its own file and then + * remove PMD.TooManyMethods suppression */ @SuppressWarnings("PMD.TooManyMethods") public interface RqHeaders extends Request { @@ -212,10 +214,11 @@ public String single(final CharSequence name) throws IOException { return params.next(); } /** - * Get param or default. - * @param name Name of query param - * @param def Default, if not found - * @return Value of it + * If header is present, returns the first header value. + * If not, returns a default value. + * @param name Name of header key + * @param def Default value + * @return Header Value or default value * @throws IOException If fails */ public String single(final CharSequence name, final CharSequence def) diff --git a/src/test/java/org/takes/rq/RqHeadersTest.java b/src/test/java/org/takes/rq/RqHeadersTest.java index 3d04e0eaf..20cdb2837 100644 --- a/src/test/java/org/takes/rq/RqHeadersTest.java +++ b/src/test/java/org/takes/rq/RqHeadersTest.java @@ -81,11 +81,11 @@ public void findsAllHeaders() throws IOException { } /** - * RqHeaders can find single header. + * RqHeaders.Smart can find single header. * @throws IOException If some problem inside */ @Test - public void findsSingleHeader() throws IOException { + public void returnsSingleHeader() throws IOException { MatcherAssert.assertThat( new RqHeaders.Smart( new RqHeaders.Base( @@ -97,18 +97,17 @@ public void findsSingleHeader() throws IOException { "" ) ) - // @checkstyle MultipleStringLiteralsCheck (1 line) ).single("host", "www.takes.net"), Matchers.equalTo("www.takes.com") ); } /** - * RqHeaders can find default header. + * RqHeaders.Smart can find default header. * @throws IOException If some problem inside */ @Test - public void findsDefaultHeader() throws IOException { - final String host = "www.takes.org"; + public void returnsDefaultHeader() throws IOException { + final String type = "text/plain"; MatcherAssert.assertThat( new RqHeaders.Smart( new RqHeaders.Base( @@ -120,9 +119,8 @@ public void findsDefaultHeader() throws IOException { "" ) ) - // @checkstyle MultipleStringLiteralsCheck (1 line) - ).single("host", host), - Matchers.equalTo(host) + ).single("Content-type", type), + Matchers.equalTo(type) ); } From 7580b92d26499cac8b11f7ba140d04b0cda70217 Mon Sep 17 00:00:00 2001 From: erim erturk Date: Mon, 15 Jun 2015 17:01:50 +0300 Subject: [PATCH 41/48] #342 refactor javadoc --- src/test/java/org/takes/rq/RqHeadersTest.java | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/test/java/org/takes/rq/RqHeadersTest.java b/src/test/java/org/takes/rq/RqHeadersTest.java index 20cdb2837..6cced3d30 100644 --- a/src/test/java/org/takes/rq/RqHeadersTest.java +++ b/src/test/java/org/takes/rq/RqHeadersTest.java @@ -81,7 +81,7 @@ public void findsAllHeaders() throws IOException { } /** - * RqHeaders.Smart can find single header. + * RqHeaders.Smart can return a single header. * @throws IOException If some problem inside */ @Test @@ -102,7 +102,7 @@ public void returnsSingleHeader() throws IOException { ); } /** - * RqHeaders.Smart can find default header. + * RqHeaders.Smart can return a default header. * @throws IOException If some problem inside */ @Test From 348a899b550b28db9e913cd25a8a40ff9212006c Mon Sep 17 00:00:00 2001 From: erim erturk Date: Tue, 16 Jun 2015 11:22:40 +0300 Subject: [PATCH 42/48] #342 removed refactor PDD --- src/main/java/org/takes/rq/RqHeaders.java | 2 -- 1 file changed, 2 deletions(-) diff --git a/src/main/java/org/takes/rq/RqHeaders.java b/src/main/java/org/takes/rq/RqHeaders.java index c1f179362..852449ad0 100644 --- a/src/main/java/org/takes/rq/RqHeaders.java +++ b/src/main/java/org/takes/rq/RqHeaders.java @@ -48,8 +48,6 @@ * @author Yegor Bugayenko (yegor@teamed.io) * @version $Id$ * @since 0.1 - * @todo #342:30min/DEV Extract RqHeaders.Smart into its own file and then - * remove PMD.TooManyMethods suppression */ @SuppressWarnings("PMD.TooManyMethods") public interface RqHeaders extends Request { From 64a796ebccd47391c8682a388a23f117087b1173 Mon Sep 17 00:00:00 2001 From: Dmitry Zaytsev Date: Fri, 19 Jun 2015 00:15:22 +0500 Subject: [PATCH 43/48] #346 added JsonWriter.close() --- src/main/java/org/takes/rs/RsJSON.java | 5 ++++- src/test/java/org/takes/rs/RsJSONTest.java | 22 ++++++++++++++++++++++ 2 files changed, 26 insertions(+), 1 deletion(-) diff --git a/src/main/java/org/takes/rs/RsJSON.java b/src/main/java/org/takes/rs/RsJSON.java index b00c1f237..2c8dfd5c3 100644 --- a/src/main/java/org/takes/rs/RsJSON.java +++ b/src/main/java/org/takes/rs/RsJSON.java @@ -28,6 +28,7 @@ import java.net.HttpURLConnection; import javax.json.Json; import javax.json.JsonStructure; +import javax.json.JsonWriter; import lombok.EqualsAndHashCode; import org.takes.Response; @@ -89,7 +90,9 @@ public RsJSON(final Response res) { */ private static byte[] print(final RsJSON.Source src) throws IOException { final ByteArrayOutputStream baos = new ByteArrayOutputStream(); - Json.createWriter(baos).write(src.toJSON()); + final JsonWriter writer = Json.createWriter(baos); + writer.write(src.toJSON()); + writer.close(); return baos.toByteArray(); } diff --git a/src/test/java/org/takes/rs/RsJSONTest.java b/src/test/java/org/takes/rs/RsJSONTest.java index d6d734c98..e8d2a7965 100644 --- a/src/test/java/org/takes/rs/RsJSONTest.java +++ b/src/test/java/org/takes/rs/RsJSONTest.java @@ -25,6 +25,7 @@ import java.io.IOException; import javax.json.Json; +import javax.json.JsonArrayBuilder; import javax.json.JsonStructure; import org.hamcrest.MatcherAssert; import org.hamcrest.Matchers; @@ -56,4 +57,25 @@ public void buildsJsonResponse() throws IOException { ); } + /** + * RsJSON can build a big JSON response. + * @throws IOException If some problem inside + */ + @Test + public void buildsBigJsonResponse() throws IOException { + final int size = 100000; + final JsonArrayBuilder builder = Json.createArrayBuilder(); + for (int idx = 0; idx < size; ++idx) { + builder.add( + Json.createObjectBuilder().add("number", "212 555-1234") + ); + } + MatcherAssert.assertThat( + Json.createReader( + new RsJSON(builder.build()).body() + ).readArray().size(), + Matchers.equalTo(size) + ); + } + } From 5b562381321afb201bf3091b77e7e22abdbbaac4 Mon Sep 17 00:00:00 2001 From: Dmitry Zaytsev Date: Fri, 19 Jun 2015 05:34:48 +0500 Subject: [PATCH 44/48] #346 try/finally --- src/main/java/org/takes/rs/RsJSON.java | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/src/main/java/org/takes/rs/RsJSON.java b/src/main/java/org/takes/rs/RsJSON.java index 2c8dfd5c3..6a52a1973 100644 --- a/src/main/java/org/takes/rs/RsJSON.java +++ b/src/main/java/org/takes/rs/RsJSON.java @@ -91,9 +91,12 @@ public RsJSON(final Response res) { private static byte[] print(final RsJSON.Source src) throws IOException { final ByteArrayOutputStream baos = new ByteArrayOutputStream(); final JsonWriter writer = Json.createWriter(baos); - writer.write(src.toJSON()); - writer.close(); - return baos.toByteArray(); + try { + writer.write(src.toJSON()); + return baos.toByteArray(); + } finally { + writer.close(); + } } /** From d00f4891b55b8bfeab0c40e6873793619fe4c6fd Mon Sep 17 00:00:00 2001 From: Dmitry Zaytsev Date: Fri, 19 Jun 2015 08:44:11 +0500 Subject: [PATCH 45/48] #346 moved return point --- src/main/java/org/takes/rs/RsJSON.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/main/java/org/takes/rs/RsJSON.java b/src/main/java/org/takes/rs/RsJSON.java index 6a52a1973..4c563706b 100644 --- a/src/main/java/org/takes/rs/RsJSON.java +++ b/src/main/java/org/takes/rs/RsJSON.java @@ -93,10 +93,10 @@ private static byte[] print(final RsJSON.Source src) throws IOException { final JsonWriter writer = Json.createWriter(baos); try { writer.write(src.toJSON()); - return baos.toByteArray(); } finally { writer.close(); } + return baos.toByteArray(); } /** From e9954c3f0786b8947ea50b6836d12a44bc67a7ac Mon Sep 17 00:00:00 2001 From: Yegor Bugayenko Date: Fri, 19 Jun 2015 13:57:06 -0700 Subject: [PATCH 46/48] #351 documented XeStylesheet --- src/main/java/org/takes/rs/xe/XeStylesheet.java | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/src/main/java/org/takes/rs/xe/XeStylesheet.java b/src/main/java/org/takes/rs/xe/XeStylesheet.java index 5def98fb9..b2d0b4670 100644 --- a/src/main/java/org/takes/rs/xe/XeStylesheet.java +++ b/src/main/java/org/takes/rs/xe/XeStylesheet.java @@ -35,12 +35,25 @@ * @author Yegor Bugayenko (yegor@teamed.io) * @version $Id$ * @since 0.1 + * @link XML+XSLT in a Browser + * @link RESTful API and a Web Site in the Same URL */ @EqualsAndHashCode(callSuper = true) public final class XeStylesheet extends XeWrap { /** * Ctor. + * + *

The only argument here is a location of XSL stylesheet, + * in resources and, at the same time, in HTTP. For example, you + * can put "/xsl/main.xsl" there. In the XML output this will generate + * an "xml-stylesheet" annotation, which will point the browser + * to "http://yoursite/xsl/main.xsl". Obviously, this page must + * be available. On the other hand, if you're using RsXSLT, this + * file must be available in resources as "/xsl/main.xsl". + * + *

It is recommended to put XSL files under "src/main/resources/xsl". + * * @param xsl XSL stylesheet */ public XeStylesheet(final CharSequence xsl) { From 21f80dd8dd43f21d1dec171cb5e0f670fad47b20 Mon Sep 17 00:00:00 2001 From: Yegor Bugayenko Date: Mon, 22 Jun 2015 15:34:01 -0700 Subject: [PATCH 47/48] .gitattributes --- .gitattributes | 24 +++++------------------- 1 file changed, 5 insertions(+), 19 deletions(-) diff --git a/.gitattributes b/.gitattributes index 3a53f356e..b96f9a74d 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,20 +1,6 @@ -# Check out all text files in UNIX format. -* text eol=lf +# Check out all text files in UNIX format, with LF as end of line +# Don't change this file. If you have any ideas about it, please +# submit a separate issue about it and we'll discuss. There has to +# be a single line here: -# Explicitly declare text files we want to always be normalized and converted -# to native line endings on checkout. -*.txt text -*.java text -*.groovy text -*.xml text -*.md text -*.pom text -*.properties text -*.tex text -*.vm text -*.xsl text -*.yml text - -# Denote all files that are truly binary and should not be modified. -*.png binary -*.jpg binary +* text=auto eol=lf From 7de325dce20c4fa333c2c8b65300ef4d238005dd Mon Sep 17 00:00:00 2001 From: Yegor Bugayenko Date: Mon, 22 Jun 2015 15:51:15 -0700 Subject: [PATCH 48/48] .gitattributes with IDENT --- .gitattributes | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/.gitattributes b/.gitattributes index b96f9a74d..592616cca 100644 --- a/.gitattributes +++ b/.gitattributes @@ -1,6 +1,7 @@ # Check out all text files in UNIX format, with LF as end of line # Don't change this file. If you have any ideas about it, please -# submit a separate issue about it and we'll discuss. There has to -# be a single line here: +# submit a separate issue about it and we'll discuss. * text=auto eol=lf +*.java ident +*.xml ident