Skip to content

Commit 88b763b

Browse files
authored
Merge pull request #673 from drallgood/fix/672-transit-provider-website-url
Fix transitProviderWebsiteURL naming + deprecate old API (#672) closes #672
2 parents 182553f + 12e3742 commit 88b763b

6 files changed

Lines changed: 227 additions & 3 deletions

File tree

jpasskit/src/main/java/de/brendamour/jpasskit/PKPass.java

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -15,6 +15,10 @@
1515
*/
1616
package de.brendamour.jpasskit;
1717

18+
import com.fasterxml.jackson.annotation.JsonAlias;
19+
import com.fasterxml.jackson.annotation.JsonIgnore;
20+
import com.fasterxml.jackson.annotation.JsonProperty;
21+
1822
import java.io.Serializable;
1923
import java.net.URL;
2024
import java.time.Instant;
@@ -103,7 +107,9 @@ public class PKPass implements Cloneable, Serializable {
103107
protected URL managementURL;
104108
protected String transitProviderPhoneNumber;
105109
protected String transitProviderEmail;
106-
protected URL transitProviderWebsiteUrl;
110+
@JsonProperty("transitProviderWebsiteURL")
111+
@JsonAlias("transitProviderWebsiteUrl")
112+
protected URL transitProviderWebsiteURL;
107113
protected URL upgradeURL;
108114
protected URL bagPolicyURL;
109115
protected URL accessibilityURL;
@@ -284,8 +290,13 @@ public String getTransitProviderPhoneNumber() {
284290
public String getTransitProviderEmail() {
285291
return transitProviderEmail;
286292
}
293+
@Deprecated
294+
@JsonIgnore
287295
public URL getTransitProviderWebsiteUrl() {
288-
return transitProviderWebsiteUrl;
296+
return getTransitProviderWebsiteURL();
297+
}
298+
public URL getTransitProviderWebsiteURL() {
299+
return transitProviderWebsiteURL;
289300
}
290301
public URL getUpgradeURL() {
291302
return upgradeURL;

jpasskit/src/main/java/de/brendamour/jpasskit/PKPassBuilder.java

Lines changed: 6 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -421,8 +421,13 @@ public PKPassBuilder transitProviderEmail(String transitProviderEmail) {
421421
return this;
422422
}
423423

424+
@Deprecated
424425
public PKPassBuilder transitProviderWebsiteUrl(URL transitProviderWebsiteUrl) {
425-
this.pkPass.transitProviderWebsiteUrl = transitProviderWebsiteUrl;
426+
return transitProviderWebsiteURL(transitProviderWebsiteUrl);
427+
}
428+
429+
public PKPassBuilder transitProviderWebsiteURL(URL transitProviderWebsiteURL) {
430+
this.pkPass.transitProviderWebsiteURL = transitProviderWebsiteURL;
426431
return this;
427432
}
428433

jpasskit/src/test/java/de/brendamour/jpasskit/PKPassBuilderTest.java

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -513,6 +513,35 @@ public void testWebServiceURLSetter() {
513513
}
514514
}
515515

516+
@Test
517+
public void testTransitProviderWebsiteURLBuilderMethods() throws MalformedURLException {
518+
URL website = new URL("https://example.com/transit");
519+
520+
PKPass pass = builder
521+
.serialNumber("123")
522+
.passTypeIdentifier("com.test.pass")
523+
.teamIdentifier("TEAM123")
524+
.description("Test Pass")
525+
.organizationName("Test Org")
526+
.pass(PKGenericPass.builder())
527+
.transitProviderWebsiteURL(website)
528+
.build();
529+
530+
Assert.assertEquals(pass.getTransitProviderWebsiteURL(), website);
531+
532+
PKPass passFromDeprecated = PKPass.builder()
533+
.serialNumber("123")
534+
.passTypeIdentifier("com.test.pass")
535+
.teamIdentifier("TEAM123")
536+
.description("Test Pass")
537+
.organizationName("Test Org")
538+
.pass(PKGenericPass.builder())
539+
.transitProviderWebsiteUrl(website)
540+
.build();
541+
542+
Assert.assertEquals(passFromDeprecated.getTransitProviderWebsiteURL(), website);
543+
}
544+
516545
@Test
517546
public void testExpirationDateSetter() {
518547
Date expirationDate = new Date();

jpasskit/src/test/java/de/brendamour/jpasskit/PKPassTest.java

Lines changed: 34 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -378,6 +378,40 @@ public void test_passSemanticsSerialization() {
378378
});
379379
}
380380

381+
@Test
382+
public void test_transitProviderWebsiteURL_serializationAndAliasDeserialization() throws MalformedURLException {
383+
var website = new URL("https://example.com/transit");
384+
var pass = this.builder
385+
.serialNumber("123")
386+
.passTypeIdentifier("com.test.pass")
387+
.teamIdentifier("TEAM123")
388+
.description("Test Pass")
389+
.organizationName("Test Org")
390+
.pass(PKGenericPass.builder())
391+
.transitProviderWebsiteURL(website)
392+
.build();
393+
394+
assertThat(pass.getTransitProviderWebsiteURL()).isEqualTo(website);
395+
assertThat(pass.getTransitProviderWebsiteUrl()).isEqualTo(website);
396+
397+
var mapper = new ObjectMapper();
398+
var serializedPass = mapper.convertValue(pass, Map.class);
399+
assertThat(serializedPass).containsKey("transitProviderWebsiteURL");
400+
assertThat(serializedPass).doesNotContainKey("transitProviderWebsiteUrl");
401+
402+
var aliasInput = ImmutableMap.<String, Object>of(
403+
"serialNumber", "123",
404+
"passTypeIdentifier", "com.test.pass",
405+
"teamIdentifier", "TEAM123",
406+
"description", "Test Pass",
407+
"organizationName", "Test Org",
408+
"generic", ImmutableMap.of(),
409+
"transitProviderWebsiteUrl", website.toString()
410+
);
411+
var fromAlias = mapper.convertValue(aliasInput, PKPass.class);
412+
assertThat(fromAlias.getTransitProviderWebsiteURL()).isEqualTo(website);
413+
}
414+
381415
private static URL asUrl(String value) {
382416
try {
383417
return new URL(value);
Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
/**
2+
* Copyright (C) 2024 Patrice Brend'amour <patrice@brendamour.net>
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package de.brendamour.jpasskit.signing;
17+
18+
import org.testng.Assert;
19+
import org.testng.annotations.Test;
20+
21+
import java.io.ByteArrayInputStream;
22+
import java.io.InputStream;
23+
import java.security.cert.CertificateException;
24+
import java.nio.charset.StandardCharsets;
25+
26+
import static org.assertj.core.api.Assertions.assertThat;
27+
import static org.assertj.core.api.Assertions.assertThatThrownBy;
28+
29+
public class PKSigningInformationUtilTest {
30+
31+
private static final String KEYSTORE_PATH = "passbook/jpasskittest.p12";
32+
private static final String KEYSTORE_PASSWORD = "password";
33+
private static final String APPLE_WWDRCA_CERT_PATH = "passbook/ca-chain.cert.pem";
34+
35+
@Test
36+
public void testLoadSigningInformationFromPKCS12AndIntermediateCertificate_classpath() throws Exception {
37+
PKSigningInformation info = new PKSigningInformationUtil()
38+
.loadSigningInformationFromPKCS12AndIntermediateCertificate(KEYSTORE_PATH, KEYSTORE_PASSWORD, APPLE_WWDRCA_CERT_PATH);
39+
40+
Assert.assertNotNull(info);
41+
Assert.assertTrue(info.isValid());
42+
Assert.assertNotNull(info.getSigningPrivateKey());
43+
Assert.assertNotNull(info.getSigningCert());
44+
Assert.assertNotNull(info.getAppleWWDRCACert());
45+
}
46+
47+
@Test
48+
public void testLoadSigningInformation_wrapsExceptions() {
49+
assertThatThrownBy(() -> new PKSigningInformationUtil()
50+
.loadSigningInformation("does-not-exist.p12", "password", "passbook/ca-chain.cert.pem"))
51+
.isInstanceOf(PKSigningException.class)
52+
.hasMessage("Failed to load signing information");
53+
}
54+
55+
@Test
56+
public void testDeprecatedLoadDERCertificate_invalidInputStreamThrowsIOException() {
57+
InputStream invalid = new ByteArrayInputStream("not-a-cert".getBytes(StandardCharsets.UTF_8));
58+
59+
assertThatThrownBy(() -> new PKSigningInformationUtil().loadDERCertificate(invalid))
60+
.isInstanceOfAny(CertificateException.class, java.io.IOException.class);
61+
}
62+
63+
@Test
64+
public void testDeprecatedLoadPKCS12File_invalidInputStreamThrowsIOException() {
65+
InputStream invalid = new ByteArrayInputStream("not-a-keystore".getBytes(StandardCharsets.UTF_8));
66+
67+
assertThatThrownBy(() -> new PKSigningInformationUtil().loadPKCS12File(invalid, "password"))
68+
.isInstanceOfAny(CertificateException.class, java.io.IOException.class);
69+
}
70+
}
Lines changed: 75 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,75 @@
1+
/**
2+
* Copyright (C) 2024 Patrice Brend'amour <patrice@brendamour.net>
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License");
5+
* you may not use this file except in compliance with the License.
6+
* You may obtain a copy of the License at
7+
*
8+
* http://www.apache.org/licenses/LICENSE-2.0
9+
*
10+
* Unless required by applicable law or agreed to in writing, software
11+
* distributed under the License is distributed on an "AS IS" BASIS,
12+
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13+
* See the License for the specific language governing permissions and
14+
* limitations under the License.
15+
*/
16+
package de.brendamour.jpasskit.util;
17+
18+
import org.apache.commons.lang3.tuple.ImmutablePair;
19+
import org.testng.Assert;
20+
import org.testng.annotations.Test;
21+
22+
import java.io.FileNotFoundException;
23+
import java.io.InputStream;
24+
import java.security.KeyStore;
25+
import java.security.PrivateKey;
26+
import java.security.cert.X509Certificate;
27+
28+
import static org.assertj.core.api.Assertions.assertThat;
29+
import static org.assertj.core.api.Assertions.assertThatThrownBy;
30+
31+
public class CertUtilsTest {
32+
33+
private static final String KEYSTORE_PATH = "passbook/jpasskittest.p12";
34+
private static final char[] KEYSTORE_PASSWORD = "password".toCharArray();
35+
private static final String CERTIFICATE_PATH = "passbook/ca-chain.cert.pem";
36+
private static final String CERTIFICATE_WITH_UID_PATH = "passbook/expired_cert.p12";
37+
38+
@Test
39+
public void testToInputStream_missingFileThrows() {
40+
assertThatThrownBy(() -> CertUtils.toInputStream("does-not-exist.p12"))
41+
.isInstanceOf(FileNotFoundException.class);
42+
}
43+
44+
@Test
45+
public void testToKeyStore_extractCertificateWithKey() throws Exception {
46+
try (InputStream keyStoreStream = CertUtils.toInputStream(KEYSTORE_PATH)) {
47+
KeyStore keyStore = CertUtils.toKeyStore(keyStoreStream, KEYSTORE_PASSWORD);
48+
ImmutablePair<PrivateKey, X509Certificate> pair = CertUtils.extractCertificateWithKey(keyStore, KEYSTORE_PASSWORD);
49+
50+
assertThat(pair.getLeft()).isNotNull();
51+
assertThat(pair.getRight()).isNotNull();
52+
}
53+
}
54+
55+
@Test
56+
public void testToX509Certificate_andExtractApnsTopics() throws Exception {
57+
try (InputStream certificateStream = CertUtils.toInputStream(CERTIFICATE_PATH)) {
58+
X509Certificate certificate = CertUtils.toX509Certificate(certificateStream);
59+
assertThat(certificate).isNotNull();
60+
61+
// This certificate typically doesn't contain the Pass-specific topic extension; still exercises null-extension branch.
62+
assertThat(CertUtils.extractApnsTopics(certificate)).isNotNull();
63+
}
64+
65+
// Use an APNS-like Pass certificate with UID in subject to exercise UID extraction.
66+
try (InputStream expiredCertP12 = CertUtils.toInputStream(CERTIFICATE_WITH_UID_PATH)) {
67+
KeyStore keyStore = CertUtils.toKeyStore(expiredCertP12, "cert".toCharArray());
68+
ImmutablePair<PrivateKey, X509Certificate> pair = CertUtils.extractCertificateWithKey(keyStore, "cert".toCharArray());
69+
70+
// May be expired; extractApnsTopics doesn't validate.
71+
var topics = CertUtils.extractApnsTopics(pair.getRight());
72+
Assert.assertNotNull(topics);
73+
}
74+
}
75+
}

0 commit comments

Comments
 (0)