Skip to content

Commit bef00f5

Browse files
committed
CAUSEWAY-3939: Viewmodel Bookmark Overhaul
2 parents 5210bcb + e66290f commit bef00f5

File tree

44 files changed

+1200
-1090
lines changed

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

44 files changed

+1200
-1090
lines changed

api/applib/src/main/java/org/apache/causeway/applib/ViewModel.java

Lines changed: 11 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -27,10 +27,10 @@
2727

2828
/**
2929
* Indicates that an object belongs to the UI/application layer and is intended to be used as a view-model.
30-
* <p>
31-
* Instances of {@link ViewModel} must include (at least) one public constructor.
32-
* <p>
33-
* Contract:
30+
*
31+
* <p> Instances of {@link ViewModel} must include (at least) one public constructor.
32+
*
33+
* <p> Contract:
3434
* <ul>
3535
* <li>there is either exactly one public constructor or if there are more than one,
3636
* then only one of these is annotated with any of {@code @Inject} or {@code @Autowired(required=true)}
@@ -44,8 +44,8 @@
4444
* Naturally this also allows for the idiom of passing in the {@link ServiceInjector} as an argument
4545
* and programmatically resolve any field-style injection points via {@link ServiceInjector#injectServicesInto(Object)},
4646
* that is, if already required during <i>construction</i>.
47-
* <p>
48-
* After a view-model got new-ed up by the framework (or programmatically via the {@link FactoryService}),
47+
*
48+
* <p> After a view-model got new-ed up by the framework (or programmatically via the {@link FactoryService}),
4949
* {@link ServiceInjector#injectServicesInto(Object)} is called on the viewmodel instance,
5050
* regardless of what happened during <i>construction</i>.
5151
*
@@ -55,13 +55,12 @@ public interface ViewModel {
5555

5656
/**
5757
* Obtain a memento of the view-model. (Optional)
58-
* <p>
59-
* Captures the state of this view-model as {@link String},
58+
*
59+
* <p> Captures the state of this view-model as {@link String},
6060
* which can be passed in to this view-model's constructor for later re-construction.
61-
* <p>
62-
* The framework automatically takes care of non-URL-safe strings,
63-
* by passing them through {@link java.net.URLEncoder}/
64-
* {@link java.net.URLDecoder} for encoding and decoding respectively.
61+
*
62+
* <p> The framework automatically takes care of non-URL-safe strings, null and the empty String.
63+
* Those view-model mementos are also digitally signed, before the are handed out to clients.
6564
*/
6665
@Programmatic
6766
String viewModelMemento();

api/applib/src/main/java/org/apache/causeway/applib/exceptions/unrecoverable/BookmarkNotFoundException.java

Lines changed: 0 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -25,12 +25,8 @@
2525
* Indicates that a bookmark cannot be found.
2626
*
2727
* @since 2.1, 3.1 {@index}
28-
*
2928
*/
3029
public class BookmarkNotFoundException extends UnrecoverableException {
31-
/**
32-
*
33-
*/
3430
private static final long serialVersionUID = 1L;
3531

3632
public BookmarkNotFoundException(final String msg) {
Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,57 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
package org.apache.causeway.applib.exceptions.unrecoverable;
20+
21+
import org.apache.causeway.applib.exceptions.UnrecoverableException;
22+
import org.apache.causeway.applib.services.i18n.TranslatableString;
23+
24+
/**
25+
* Indicates that a digital verification failed.
26+
*
27+
* <p>E.g. could be an invalid (or no longer valid) bookmark.
28+
*
29+
* @since 3.5 {@index}
30+
*
31+
*/
32+
public class DigitalVerificationException extends UnrecoverableException {
33+
private static final long serialVersionUID = 1L;
34+
35+
public DigitalVerificationException(final String msg) {
36+
super(msg);
37+
}
38+
39+
public DigitalVerificationException(final TranslatableString translatableMessage,
40+
final Class<?> translationContextClass, final String translationContextMethod) {
41+
super(translatableMessage, translationContextClass, translationContextMethod);
42+
}
43+
44+
public DigitalVerificationException(final Throwable cause) {
45+
super(cause);
46+
}
47+
48+
public DigitalVerificationException(final String msg, final Throwable cause) {
49+
super(msg, cause);
50+
}
51+
52+
public DigitalVerificationException(final TranslatableString translatableMessage,
53+
final Class<?> translationContextClass, final String translationContextMethod, final Throwable cause) {
54+
super(translatableMessage, translationContextClass, translationContextMethod, cause);
55+
}
56+
57+
}

api/applib/src/main/java/org/apache/causeway/applib/layout/grid/bootstrap/BSUtil.java

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -21,6 +21,8 @@
2121
import java.util.Optional;
2222
import java.util.concurrent.atomic.AtomicBoolean;
2323

24+
import org.springframework.util.SerializationUtils;
25+
2426
import org.apache.causeway.applib.layout.component.ActionLayoutData;
2527
import org.apache.causeway.applib.layout.component.ActionLayoutDataOwner;
2628
import org.apache.causeway.applib.layout.component.CollectionLayoutData;
@@ -30,9 +32,6 @@
3032
import org.apache.causeway.applib.layout.component.FieldSet;
3133
import org.apache.causeway.applib.layout.component.PropertyLayoutData;
3234
import org.apache.causeway.applib.layout.grid.bootstrap.BSElement.BSElementVisitor;
33-
import org.apache.causeway.commons.internal.base._Casts;
34-
import org.apache.causeway.commons.internal.resources._Serializables;
35-
3635
import lombok.experimental.UtilityClass;
3736

3837
@UtilityClass
@@ -76,9 +75,7 @@ public void visit(final CollectionLayoutData collectionLayoutData) {
7675
* Creates a deep copy of given original grid.
7776
*/
7877
public BSGrid deepCopy(final BSGrid orig) {
79-
var bytes = _Serializables.write(orig);
80-
return _Casts.uncheckedCast(
81-
_Serializables.read(BSGrid.class, bytes));
78+
return SerializationUtils.clone(orig);
8279
}
8380

8481
public BSGrid resolveOwners(final BSGrid grid) {
Lines changed: 123 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,123 @@
1+
/*
2+
* Licensed to the Apache Software Foundation (ASF) under one
3+
* or more contributor license agreements. See the NOTICE file
4+
* distributed with this work for additional information
5+
* regarding copyright ownership. The ASF licenses this file
6+
* to you under the Apache License, Version 2.0 (the
7+
* "License"); you may not use this file except in compliance
8+
* with the License. You may obtain a copy of the License at
9+
*
10+
* http://www.apache.org/licenses/LICENSE-2.0
11+
*
12+
* Unless required by applicable law or agreed to in writing,
13+
* software distributed under the License is distributed on an
14+
* "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
15+
* KIND, either express or implied. See the License for the
16+
* specific language governing permissions and limitations
17+
* under the License.
18+
*/
19+
package org.apache.causeway.applib.services.bookmark;
20+
21+
import java.security.SecureRandom;
22+
import java.util.Arrays;
23+
import java.util.Objects;
24+
import javax.crypto.Mac;
25+
import javax.crypto.spec.SecretKeySpec;
26+
27+
import org.jspecify.annotations.Nullable;
28+
29+
import lombok.SneakyThrows;
30+
31+
/**
32+
* Can be registered with Spring, to override the built in default, which has an application scoped random secret.
33+
*
34+
* <pre>
35+
* {@code @Configuration}
36+
* class EnableHmacAuthority {
37+
* {@code @Bean}
38+
* public HmacAuthority hmacAuthority() {
39+
* return HmacAuthority.HmacSHA256.randomInstance();
40+
* }
41+
* }
42+
* </pre>
43+
*
44+
* <p>Used to generate the bookmark of view models so that they are not susceptible to forgery or de-serialization attacks.
45+
* (Bookmarks of entities are not susceptible).
46+
* Note though, that the bookmarks' validity is bound to the (server-side) secret key of the {@link HmacAuthority}.
47+
* Once the secret changes, bookmarks that are stored client-side for later use, will be rendered invalid.
48+
*
49+
* @apiNote the default bean is auto-configured with 'CausewayModuleCoreRuntimeServices.HmacAuthorityAutoconfigure'
50+
*
51+
* @since 3.5
52+
*/
53+
public interface HmacAuthority {
54+
55+
/**
56+
* HMAC as byte array, for given input data.
57+
*/
58+
byte[] generateHmac(byte[] data);
59+
60+
/**
61+
* Verifies that given dataToVerify when passed to {@link #generateHmac(byte[])} yields given hmacToVerify.
62+
*
63+
* <p>If any of the arguments is null returns false.
64+
*
65+
* <p>If hmacToVerify does not conform with {@link #isValidHmacLength(int)} returns false.
66+
*/
67+
default boolean verifyHmac(final @Nullable byte[] data, final @Nullable byte[] hmacToVerify) {
68+
if(data == null || hmacToVerify == null) return false; // invalid by definition
69+
if(!isValidHmacLength(hmacToVerify.length)) return false; // shortcut
70+
return Arrays.equals(generateHmac(data), hmacToVerify);
71+
}
72+
73+
/**
74+
* Whether HMAC length in bytes is expected/valid.
75+
*/
76+
boolean isValidHmacLength(int hmacLength);
77+
78+
// -- IMPL
79+
80+
record HmacSHA256(
81+
SecretKeySpec secretKey) implements HmacAuthority {
82+
83+
private final static String ALGORITHM = "HmacSHA256";
84+
85+
public HmacSHA256(final byte[] secret) {
86+
this(new SecretKeySpec(secret, ALGORITHM));
87+
}
88+
89+
@SneakyThrows
90+
public static HmacSHA256 randomInstance() {
91+
var secret = new byte[32]; // double the minimum requirement of 16
92+
SecureRandom.getInstanceStrong().nextBytes(secret);
93+
return new HmacSHA256(secret);
94+
}
95+
96+
@Override
97+
public byte[] generateHmac(final byte[] data) {
98+
Objects.requireNonNull(data);
99+
var mac = newMac();
100+
return mac.doFinal(data);
101+
}
102+
103+
@Override
104+
public boolean isValidHmacLength(final int hmacLength) {
105+
return 32 == hmacLength;
106+
}
107+
108+
// -- HELPER
109+
110+
@SneakyThrows
111+
private Mac newMac() {
112+
var mac = Mac.getInstance(ALGORITHM);
113+
mac.init(secretKey);
114+
return mac;
115+
}
116+
}
117+
118+
// JUNIT SUPPORT
119+
120+
static HmacAuthority forTesting() {
121+
return new HmacSHA256("secret for testing only".getBytes());
122+
}
123+
}

api/applib/src/main/java/org/apache/causeway/applib/services/urlencoding/UrlEncodingService.java

Lines changed: 9 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -22,39 +22,32 @@
2222

2323
import org.apache.causeway.commons.internal.base._Bytes;
2424
import org.apache.causeway.commons.internal.base._Strings;
25-
import org.apache.causeway.commons.internal.memento._Mementos.EncoderDecoder;
2625

2726
/**
2827
* Defines a consistent way to convert strings to/from a form safe for use
2928
* within a URL.
3029
*
31-
* <p>
32-
* The service is used by the framework to map mementos (derived from the
33-
* state of the view model itself) into a form that can be used as a view
34-
* model. When the framework needs to recreate the view model (for example
35-
* to invoke an action on it), this URL is converted back into a view model
36-
* memento, from which the view model can be hydrated.
37-
* </p>
30+
* <p>The service is used by the framework to map mementos (derived from the
31+
* state of the view model itself) into a form that can be used as a view
32+
* model. When the framework needs to recreate the view model (for example
33+
* to invoke an action on it), this URL is converted back into a view model
34+
* memento, from which the view model can be hydrated.
3835
*
3936
* @since 1.x {@index}
4037
*/
41-
public interface UrlEncodingService extends EncoderDecoder {
38+
public interface UrlEncodingService {
4239

4340
/**
44-
* Converts the string (eg view model memento) into a string safe for use
41+
* Converts given data bytes (eg view model memento) into a string safe for use
4542
* within an URL
4643
*/
47-
@Override
4844
String encode(final byte[] bytes);
4945

5046
/**
51-
* Unconverts the string from its URL form into its original form URL.
47+
* Converts the plain URL safe string back to its original data bytes.
5248
*
53-
* <p>
54-
* Reciprocal of {@link #encode(byte[])}.
55-
* </p>
49+
* <p>Inverse of {@link #encode(byte[])}.
5650
*/
57-
@Override
5851
byte[] decode(String str);
5952

6053
default String encodeString(final String str) {
@@ -101,7 +94,6 @@ public byte[] decode(final String str) {
10194
return _Bytes.ofUrlBase64.apply(_Strings.toBytes(str, StandardCharsets.UTF_8));
10295
}
10396
};
104-
10597
}
10698

10799
}

api/applib/src/main/java/org/apache/causeway/applib/value/semantics/Converter.java

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,6 @@
1818
*/
1919
package org.apache.causeway.applib.value.semantics;
2020

21-
import org.apache.causeway.commons.internal.memento._Mementos.EncoderDecoder;
22-
2321
/**
2422
* Provides forth and back conversion between 2 types.
2523
*
@@ -28,7 +26,6 @@
2826
*
2927
* @see DefaultsProvider
3028
* @see Parser
31-
* @see EncoderDecoder
3229
* @see ValueSemanticsProvider
3330
*
3431
* @since 2.x {@index}

api/applib/src/main/java/org/apache/causeway/applib/value/semantics/DefaultsProvider.java

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,6 @@
1818
*/
1919
package org.apache.causeway.applib.value.semantics;
2020

21-
import org.apache.causeway.commons.internal.memento._Mementos.EncoderDecoder;
22-
2321
/**
2422
* Provides a mechanism for providing a default value for an object.
2523
*
@@ -40,7 +38,6 @@
4038
* the object reflectively.
4139
*
4240
* @see Parser
43-
* @see EncoderDecoder
4441
* @see ValueSemanticsProvider
4542
*
4643
* @since 1.x {@index}

api/applib/src/main/java/org/apache/causeway/applib/value/semantics/OrderRelation.java

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -18,8 +18,6 @@
1818
*/
1919
package org.apache.causeway.applib.value.semantics;
2020

21-
import org.apache.causeway.commons.internal.memento._Mementos.EncoderDecoder;
22-
2321
/**
2422
* Provides an ordering relation for a given value-type.
2523
* <p>
@@ -36,7 +34,6 @@
3634
*
3735
* @see DefaultsProvider
3836
* @see Parser
39-
* @see EncoderDecoder
4037
* @see ValueSemanticsProvider
4138
*
4239
* @since 2.x {@index}

commons/src/main/java/module-info.java

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -50,7 +50,6 @@
5050
exports org.apache.causeway.commons.internal.html;
5151
exports org.apache.causeway.commons.internal.image;
5252
exports org.apache.causeway.commons.internal.ioc;
53-
exports org.apache.causeway.commons.internal.memento;
5453
exports org.apache.causeway.commons.internal.os;
5554
exports org.apache.causeway.commons.internal.primitives;
5655
exports org.apache.causeway.commons.internal.proxy;

0 commit comments

Comments
 (0)