#18: extracted Android-specific logic out of the Base64Codec to its own class. Created a factory that can return either depending on environment. Ensured all code other than the lang package is at 100% test coverage.

This commit is contained in:
Les Hazlewood 2015-05-08 20:22:22 -07:00
parent c18e4eed05
commit 66b30e2e10
30 changed files with 330 additions and 127 deletions

47
pom.xml
View File

@ -70,8 +70,9 @@
<!-- Test Dependencies: Only required for testing when building. Not required by users at runtime: -->
<groovy.version>2.3.0-beta-2</groovy.version>
<logback.version>1.0.7</logback.version>
<easymock.version>3.1</easymock.version>
<testng.version>6.8</testng.version>
<easymock.version>3.3.1</easymock.version>
<junit.version>4.12</junit.version>
<powermock.version>1.6.2</powermock.version>
<failsafe.plugin.version>2.12.4</failsafe.plugin.version>
</properties>
@ -83,14 +84,6 @@
<artifactId>slf4j-api</artifactId>
<version>${slf4j.version}</version>
</dependency>
<!--
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>jcl-over-slf4j</artifactId>
<version>${slf4j.version}</version>
<scope>runtime</scope>
</dependency>
-->
<dependency>
<groupId>com.fasterxml.jackson.core</groupId>
<artifactId>jackson-databind</artifactId>
@ -107,8 +100,8 @@
</dependency>
<!-- Provided scope: only used in Android environments - not downloaded as a transitive dependency.
This dependency is only a stub of the actual implementation, which means we can't use it at test time
during TestNG tests. An Android environment is required to test for real. -->
This dependency is only a stub of the actual implementation, which means we can't use it at test time.
An Android environment is required to test for real. -->
<dependency>
<groupId>com.google.android</groupId>
<artifactId>android</artifactId>
@ -129,12 +122,6 @@
<version>${logback.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.testng</groupId>
<artifactId>testng</artifactId>
<version>${testng.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.codehaus.groovy</groupId>
<artifactId>groovy-all</artifactId>
@ -147,6 +134,30 @@
<version>${easymock.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.powermock</groupId>
<artifactId>powermock-module-junit4</artifactId>
<version>${powermock.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.powermock</groupId>
<artifactId>powermock-api-easymock</artifactId>
<version>${powermock.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>org.powermock</groupId>
<artifactId>powermock-core</artifactId>
<version>${powermock.version}</version>
<scope>test</scope>
</dependency>
<dependency>
<groupId>junit</groupId>
<artifactId>junit</artifactId>
<version>4.12</version>
<scope>test</scope>
</dependency>
</dependencies>

View File

@ -0,0 +1,30 @@
/*
* Copyright (C) 2015 jsonwebtoken.io
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.jsonwebtoken.impl;
public class AndroidBase64Codec extends AbstractTextCodec {
@Override
public String encode(byte[] data) {
int flags = android.util.Base64.NO_PADDING | android.util.Base64.NO_WRAP;
return android.util.Base64.encodeToString(data, flags);
}
@Override
public byte[] decode(String encoded) {
return android.util.Base64.decode(encoded, android.util.Base64.DEFAULT);
}
}

View File

@ -17,45 +17,12 @@ package io.jsonwebtoken.impl;
public class Base64Codec extends AbstractTextCodec {
private static final boolean ANDROID = isAndroid();
private static boolean isAndroid() {
String name = System.getProperty("java.vm.name");
if (name != null) {
String lcase = name.toLowerCase();
return lcase.contains("dalvik");
}
name = System.getProperty("java.vm.vendor");
if (name != null) {
String lcase = name.toLowerCase();
return lcase.contains("android");
}
return false;
}
public String encode(byte[] data) {
if (ANDROID) {
int flags = android.util.Base64.NO_PADDING | android.util.Base64.NO_WRAP;
return android.util.Base64.encodeToString(data, flags);
}
//else, assume standard JVM
return javax.xml.bind.DatatypeConverter.printBase64Binary(data);
}
@Override
public byte[] decode(String encoded) {
if (ANDROID) {
return android.util.Base64.decode(encoded, android.util.Base64.DEFAULT);
}
//else assume standard JVM:
return javax.xml.bind.DatatypeConverter.parseBase64Binary(encoded);
}
}

View File

@ -0,0 +1,51 @@
/*
* Copyright (C) 2015 jsonwebtoken.io
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.jsonwebtoken.impl;
public class DefaultTextCodecFactory implements TextCodecFactory {
protected String getSystemProperty(String key) {
return System.getProperty(key);
}
protected boolean isAndroid() {
String name = getSystemProperty("java.vm.name");
if (name != null) {
String lcase = name.toLowerCase();
return lcase.contains("dalvik");
}
name = getSystemProperty("java.vm.vendor");
if (name != null) {
String lcase = name.toLowerCase();
return lcase.contains("android");
}
return false;
}
@Override
public TextCodec getTextCodec() {
if (isAndroid()) {
return new AndroidBase64Codec();
}
return new Base64Codec();
}
}

View File

@ -17,7 +17,7 @@ package io.jsonwebtoken.impl;
public interface TextCodec {
public static final TextCodec BASE64 = new Base64Codec();
public static final TextCodec BASE64 = new DefaultTextCodecFactory().getTextCodec();
public static final TextCodec BASE64URL = new Base64UrlCodec();
String encode(String data);

View File

@ -0,0 +1,21 @@
/*
* Copyright (C) 2015 jsonwebtoken.io
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.jsonwebtoken.impl;
public interface TextCodecFactory {
TextCodec getTextCodec();
}

View File

@ -15,9 +15,8 @@
*/
package io.jsonwebtoken
import org.testng.annotations.Test
import static org.testng.Assert.*
import org.junit.Test
import static org.junit.Assert.*
class ExpiredJwtExceptionTest {

View File

@ -15,9 +15,8 @@
*/
package io.jsonwebtoken
import org.testng.annotations.Test
import static org.testng.Assert.*
import org.junit.Test
import static org.junit.Assert.*
class JwtHandlerAdapterTest {

View File

@ -16,13 +16,12 @@
package io.jsonwebtoken
import io.jsonwebtoken.impl.TextCodec
import org.testng.annotations.Test
import javax.crypto.spec.SecretKeySpec
import java.security.SecureRandom
import static org.testng.Assert.*
import org.junit.Test
import static org.junit.Assert.*
class JwtParserTest {
@ -97,7 +96,7 @@ class JwtParserTest {
Jwts.parser().setSigningKey(randomKey()).parse(bad);
fail()
} catch (SignatureException se) {
assertEquals se.getMessage(), "Unsupported signature algorithm '$badAlgorithmName'"
assertEquals se.getMessage(), "Unsupported signature algorithm '$badAlgorithmName'".toString()
}
}

View File

@ -22,7 +22,6 @@ import io.jsonwebtoken.impl.TextCodec
import io.jsonwebtoken.impl.crypto.EllipticCurveProvider
import io.jsonwebtoken.impl.crypto.MacProvider
import io.jsonwebtoken.impl.crypto.RsaProvider
import org.testng.annotations.Test
import javax.crypto.Mac
import javax.crypto.spec.SecretKeySpec
@ -31,7 +30,8 @@ import java.security.KeyPair
import java.security.PrivateKey
import java.security.PublicKey
import static org.testng.Assert.*
import org.junit.Test
import static org.junit.Assert.*
class JwtsTest {
@ -108,20 +108,20 @@ class JwtsTest {
def token = Jwts.parser().parse(jwt);
assertEquals token.body, claims
assert token.body == claims
}
@Test(expectedExceptions = IllegalArgumentException)
@Test(expected = IllegalArgumentException)
void testParseNull() {
Jwts.parser().parse(null)
}
@Test(expectedExceptions = IllegalArgumentException)
@Test(expected = IllegalArgumentException)
void testParseEmptyString() {
Jwts.parser().parse('')
}
@Test(expectedExceptions = IllegalArgumentException)
@Test(expected = IllegalArgumentException)
void testParseWhitespaceString() {
Jwts.parser().parse(' ')
}
@ -537,10 +537,8 @@ class JwtsTest {
def token = Jwts.parser().setSigningKey(key).parse(jwt);
assertEquals token.header, [alg: alg.name()]
assertEquals token.body, claims
assert [alg: alg.name()] == token.header
assert token.body == claims
}
static void testHmac(SignatureAlgorithm alg) {
@ -553,9 +551,8 @@ class JwtsTest {
def token = Jwts.parser().setSigningKey(key).parse(jwt)
assertEquals token.header, [alg: alg.name()]
assertEquals token.body, claims
assert token.header == [alg: alg.name()]
assert token.body == claims
}
static void testEC(SignatureAlgorithm alg, boolean verifyWithPrivateKey=false) {
@ -575,9 +572,8 @@ class JwtsTest {
def token = Jwts.parser().setSigningKey(key).parse(jwt);
assertEquals token.header, [alg: alg.name()]
assertEquals token.body, claims
assert token.header == [alg: alg.name()]
assert token.body == claims
}
}

View File

@ -15,9 +15,8 @@
*/
package io.jsonwebtoken
import org.testng.annotations.Test
import static org.testng.Assert.*
import org.junit.Test
import static org.junit.Assert.*
class PrematureJwtExceptionTest {

View File

@ -15,9 +15,8 @@
*/
package io.jsonwebtoken
import org.testng.annotations.Test
import static org.testng.Assert.*
import org.junit.Test
import static org.junit.Assert.*
class SignatureAlgorithmTest {
@ -38,7 +37,7 @@ class SignatureAlgorithmTest {
assert alg.description != null && alg.description != ""
}
@Test(expectedExceptions = SignatureException)
@Test(expected = SignatureException)
void testUnrecognizedAlgorithmName() {
SignatureAlgorithm.forName('whatever')
}

View File

@ -0,0 +1,72 @@
/*
* Copyright (C) 2015 jsonwebtoken.io
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.jsonwebtoken.impl
import android.util.Base64
import org.junit.Test
import org.junit.runner.RunWith
import org.powermock.core.classloader.annotations.PrepareForTest
import org.powermock.modules.junit4.PowerMockRunner
import static org.easymock.EasyMock.*
import static org.junit.Assert.*
import static org.powermock.api.easymock.PowerMock.mockStatic
import static org.powermock.api.easymock.PowerMock.replayAll
import static org.powermock.api.easymock.PowerMock.verifyAll
@RunWith(PowerMockRunner.class)
@PrepareForTest([Base64.class])
class AndroidBase64CodecTest {
@Test
public void testEncode() {
mockStatic(Base64.class);
byte[] bytes = new byte[32];
String s = "foo";
int flags = Base64.NO_PADDING | Base64.NO_WRAP;
expect(Base64.encodeToString(same(bytes), eq(flags))).andReturn(s);
replayAll();
AndroidBase64Codec codec = new AndroidBase64Codec();
String val = codec.encode(bytes);
verifyAll();
assertEquals(val, s);
}
@Test
public void testDecode() {
mockStatic(Base64.class);
byte[] bytes = new byte[32];
String s = "foo";
expect(Base64.decode((String)same(s), eq(Base64.DEFAULT))).andReturn(bytes);
replayAll();
AndroidBase64Codec codec = new AndroidBase64Codec();
def val = codec.decode(s);
verifyAll();
assertSame bytes, val
}
}

View File

@ -15,8 +15,8 @@
*/
package io.jsonwebtoken.impl
import org.testng.annotations.Test
import static org.testng.Assert.*
import org.junit.Test
import static org.junit.Assert.*
class DefaultHeaderTest {

View File

@ -15,8 +15,8 @@
*/
package io.jsonwebtoken.impl
import org.testng.annotations.Test
import static org.testng.Assert.*
import org.junit.Test
import static org.junit.Assert.*
class DefaultJwsHeaderTest {

View File

@ -17,8 +17,8 @@ package io.jsonwebtoken.impl
import io.jsonwebtoken.JwsHeader
import io.jsonwebtoken.Jwts
import org.testng.annotations.Test
import static org.testng.Assert.*
import org.junit.Test
import static org.junit.Assert.*
class DefaultJwsTest {

View File

@ -20,8 +20,8 @@ import com.fasterxml.jackson.databind.JsonMappingException
import io.jsonwebtoken.Jwts
import io.jsonwebtoken.SignatureAlgorithm
import io.jsonwebtoken.impl.crypto.MacProvider
import org.testng.annotations.Test
import static org.testng.Assert.*
import org.junit.Test
import static org.junit.Assert.*
class DefaultJwtBuilderTest {

View File

@ -0,0 +1,68 @@
/*
* Copyright (C) 2015 jsonwebtoken.io
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package io.jsonwebtoken.impl
import org.junit.Test
import org.junit.runner.RunWith
import org.powermock.core.classloader.annotations.PrepareForTest
import org.powermock.modules.junit4.PowerMockRunner
import static org.junit.Assert.*
@RunWith(PowerMockRunner.class)
@PrepareForTest([System.class])
class DefaultTextCodecFactoryTest {
@Test
void testIsAndroidByVmName() {
def factory = new DefaultTextCodecFactory() {
@Override
protected String getSystemProperty(String key) {
return 'dalvik'
}
}
assertTrue factory.getTextCodec() instanceof AndroidBase64Codec
}
@Test
void testIsAndroidByNullVmName() {
def factory = new DefaultTextCodecFactory() {
@Override
protected String getSystemProperty(String key) {
if (key == 'java.vm.name') return null;
return 'android'
}
}
assertTrue factory.getTextCodec() instanceof AndroidBase64Codec
}
@Test
void testIsAndroidByNullVmNameAndNullVendorName() {
def factory = new DefaultTextCodecFactory() {
@Override
protected String getSystemProperty(String key) {
return null
}
}
assertFalse factory.getTextCodec() instanceof AndroidBase64Codec
}
}

View File

@ -15,8 +15,8 @@
*/
package io.jsonwebtoken.impl
import org.testng.annotations.Test
import static org.testng.Assert.*
import org.junit.Test
import static org.junit.Assert.*
class JwtMapTest {
@ -115,6 +115,7 @@ class JwtMapTest {
void testValues() {
def m = new JwtMap()
m.putAll([a: 'b', c: 'd'])
assertEquals( m.values(), ['b', 'd'] as Set)
def s = ['b', 'd']
assertTrue m.values().containsAll(s) && s.containsAll(m.values())
}
}

View File

@ -16,8 +16,8 @@
package io.jsonwebtoken.impl.crypto
import io.jsonwebtoken.SignatureAlgorithm
import org.testng.annotations.Test
import static org.testng.Assert.*
import org.junit.Test
import static org.junit.Assert.*
class DefaultSignatureValidatorFactoryTest {

View File

@ -16,11 +16,8 @@
package io.jsonwebtoken.impl.crypto
import io.jsonwebtoken.SignatureAlgorithm
import org.testng.annotations.Test
import javax.crypto.spec.SecretKeySpec
import static org.testng.Assert.*
import org.junit.Test
import static org.junit.Assert.*
class DefaultSignerFactoryTest {

View File

@ -16,15 +16,14 @@
package io.jsonwebtoken.impl.crypto
import io.jsonwebtoken.SignatureAlgorithm
import org.testng.annotations.Test
import java.security.KeyPair
import java.security.NoSuchProviderException
import java.security.interfaces.ECPrivateKey
import java.security.interfaces.ECPublicKey
import static org.testng.Assert.*
import org.junit.Test
import static org.junit.Assert.*
class EllipticCurveProviderTest {

View File

@ -17,13 +17,13 @@ package io.jsonwebtoken.impl.crypto
import io.jsonwebtoken.SignatureAlgorithm
import io.jsonwebtoken.SignatureException
import org.testng.annotations.Test
import java.security.InvalidKeyException
import java.security.PublicKey
import java.security.Signature
import static org.testng.Assert.*
import org.junit.Test
import static org.junit.Assert.*
class EllipticCurveSignatureValidatorTest {

View File

@ -17,14 +17,14 @@ package io.jsonwebtoken.impl.crypto
import io.jsonwebtoken.SignatureAlgorithm
import io.jsonwebtoken.SignatureException
import org.testng.annotations.Test
import java.security.InvalidKeyException
import java.security.KeyPair
import java.security.PrivateKey
import java.security.PublicKey
import static org.testng.Assert.*
import org.junit.Test
import static org.junit.Assert.*
class EllipticCurveSignerTest {

View File

@ -16,10 +16,8 @@
package io.jsonwebtoken.impl.crypto
import io.jsonwebtoken.SignatureAlgorithm
import org.testng.annotations.Test
import static org.testng.Assert.*
import org.junit.Test
import static org.junit.Assert.*
class MacProviderTest {

View File

@ -17,8 +17,8 @@ package io.jsonwebtoken.impl.crypto
import io.jsonwebtoken.SignatureAlgorithm
import io.jsonwebtoken.SignatureException
import org.testng.annotations.Test
import static org.testng.Assert.*
import org.junit.Test
import static org.junit.Assert.*
import javax.crypto.Mac
import java.security.InvalidKeyException

View File

@ -17,7 +17,6 @@ package io.jsonwebtoken.impl.crypto
import io.jsonwebtoken.SignatureAlgorithm
import io.jsonwebtoken.SignatureException
import org.testng.annotations.Test
import java.security.InvalidAlgorithmParameterException
import java.security.KeyPair
@ -26,7 +25,8 @@ import java.security.interfaces.RSAPrivateKey
import java.security.interfaces.RSAPublicKey
import java.security.spec.PSSParameterSpec
import static org.testng.Assert.*
import org.junit.Test
import static org.junit.Assert.*
class RsaProviderTest {

View File

@ -17,7 +17,6 @@ package io.jsonwebtoken.impl.crypto
import io.jsonwebtoken.SignatureAlgorithm
import io.jsonwebtoken.SignatureException
import org.testng.annotations.Test
import java.security.InvalidKeyException
import java.security.KeyPair
@ -26,10 +25,8 @@ import java.security.PrivateKey
import java.security.PublicKey
import java.security.Signature
import static org.testng.Assert.assertEquals
import static org.testng.Assert.assertSame
import static org.testng.Assert.fail
import org.junit.Test
import static org.junit.Assert.*
class RsaSignatureValidatorTest {

View File

@ -17,7 +17,6 @@ package io.jsonwebtoken.impl.crypto
import io.jsonwebtoken.SignatureAlgorithm
import io.jsonwebtoken.SignatureException
import org.testng.annotations.Test
import javax.crypto.spec.SecretKeySpec
import java.security.InvalidKeyException
@ -26,7 +25,8 @@ import java.security.KeyPairGenerator
import java.security.PrivateKey
import java.security.PublicKey
import static org.testng.Assert.*
import org.junit.Test
import static org.junit.Assert.*
class RsaSignerTest {

View File

@ -17,12 +17,12 @@ package io.jsonwebtoken.impl.crypto
import io.jsonwebtoken.SignatureAlgorithm
import io.jsonwebtoken.SignatureException
import org.testng.annotations.Test
import java.security.NoSuchAlgorithmException
import java.security.Signature
import static org.testng.Assert.*
import org.junit.Test
import static org.junit.Assert.*
class SignatureProviderTest {