You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@causeway.apache.org by ah...@apache.org on 2026/04/08 07:42:35 UTC
(causeway) branch main updated: CAUSEWAY-3988: Support for Env Var provided HMAC Secret
This is an automated email from the ASF dual-hosted git repository.
ahuber pushed a commit to branch main
in repository https://gitbox.apache.org/repos/asf/causeway.git
The following commit(s) were added to refs/heads/main by this push:
new c004df1e76f CAUSEWAY-3988: Support for Env Var provided HMAC Secret
c004df1e76f is described below
commit c004df1e76fd2501b901b0efb35aba69e9c85f9e
Author: andi-huber <ah...@apache.org>
AuthorDate: Wed Apr 8 09:42:22 2026 +0200
CAUSEWAY-3988: Support for Env Var provided HMAC Secret
---
.../causeway/commons/internal/base/_Bytes.java | 84 ++++++++++++----------
.../causeway/commons/internal/base/_BytesTest.java | 40 +++++++++++
.../CausewayModuleCoreRuntimeServices.java | 22 +++++-
3 files changed, 106 insertions(+), 40 deletions(-)
diff --git a/commons/src/main/java/org/apache/causeway/commons/internal/base/_Bytes.java b/commons/src/main/java/org/apache/causeway/commons/internal/base/_Bytes.java
index 8e1d5a47714..696854bbd7d 100644
--- a/commons/src/main/java/org/apache/causeway/commons/internal/base/_Bytes.java
+++ b/commons/src/main/java/org/apache/causeway/commons/internal/base/_Bytes.java
@@ -25,12 +25,12 @@
import java.util.function.UnaryOperator;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
+import java.util.stream.Stream;
import java.util.zip.Deflater;
import java.util.zip.Inflater;
-import org.jspecify.annotations.Nullable;
-
import org.jspecify.annotations.NonNull;
+import org.jspecify.annotations.Nullable;
/**
* <h1>- internal use only -</h1>
@@ -59,9 +59,8 @@ private _Bytes(){}
* @throws IOException
*/
public static byte[] of(final @Nullable InputStream input) throws IOException {
- if(input==null) {
+ if(input==null)
return null;
- }
try(final ByteArrayOutputStream bos = new ByteArrayOutputStream()){
final byte[] buffer = new byte[BUFFER_SIZE];
@@ -84,9 +83,8 @@ public static byte[] of(final @Nullable InputStream input) throws IOException {
* @throws IOException
*/
public static byte[] ofKeepOpen(final @Nullable InputStream input) throws IOException {
- if(input==null) {
+ if(input==null)
return null;
- }
try(final ByteArrayOutputStream bos = new ByteArrayOutputStream()){
final byte[] buffer = new byte[BUFFER_SIZE];
@@ -110,9 +108,8 @@ public static byte[] ofKeepOpen(final @Nullable InputStream input) throws IOExce
*/
public static IntStream streamAsInts(final @Nullable byte[] bytes) {
if(bytes==null
- || bytes.length==0) {
+ || bytes.length==0)
return IntStream.empty();
- }
return IntStream.range(0, bytes.length)
.map(index->(bytes[index] & 0xff));
}
@@ -126,9 +123,8 @@ public static IntStream streamAsInts(final @Nullable byte[] bytes) {
* @see #streamAsInts(byte[])
*/
private static byte[] ofIntStream(final @Nullable IntStream intStream) {
- if(intStream==null) {
+ if(intStream==null)
return new byte[0];
- }
var listOfInts = intStream
.boxed()
.collect(Collectors.toList());
@@ -148,9 +144,8 @@ private static byte[] ofIntStream(final @Nullable IntStream intStream) {
* @see #ofHexDump(String, String)
*/
public static String hexDump(final @Nullable byte[] bytes, final @Nullable String delimiter) {
- if(bytes==null) {
+ if(bytes==null)
return "";
- }
return _Bytes.streamAsInts(bytes)
.mapToObj(Integer::toHexString)
.map(s->s.length()==1
@@ -172,9 +167,8 @@ public static String hexDump(final @Nullable byte[] bytes) {
* @see #hexDump(byte[], String)
*/
public static byte[] ofHexDump(final @Nullable String hexDump, final @Nullable String delimiter) {
- if(hexDump==null) {
+ if(hexDump==null)
return new byte[0];
- }
final int delimLen = _NullSafe.size(delimiter);
final int stride = 2 + delimLen;
@@ -211,14 +205,12 @@ public static byte[] nullToEmpty(final byte[] bytes) {
*/
public static final byte[] prepend(final @Nullable byte[] target, final @Nullable byte ... bytes) {
if(target==null) {
- if(bytes==null) {
+ if(bytes==null)
return null;
- }
return bytes.clone();
}
- if(bytes==null) {
+ if(bytes==null)
return target.clone();
- }
final byte[] result = new byte[target.length + bytes.length];
System.arraycopy(bytes, 0, result, 0, bytes.length);
System.arraycopy(target, 0, result, bytes.length, target.length);
@@ -233,14 +225,12 @@ public static final byte[] prepend(final @Nullable byte[] target, final @Nullabl
*/
public static final byte[] append(final @Nullable byte[] target, final @Nullable byte ... bytes) {
if(target==null) {
- if(bytes==null) {
+ if(bytes==null)
return null;
- }
return bytes.clone();
}
- if(bytes==null) {
+ if(bytes==null)
return target.clone();
- }
final byte[] result = new byte[target.length + bytes.length];
System.arraycopy(target, 0, result, 0, target.length);
System.arraycopy(bytes, 0, result, target.length, bytes.length);
@@ -281,12 +271,10 @@ public static final byte[] decodeBase64(final Base64.@NonNull Decoder decoder, f
* @return null if {@code input} is null
*/
public static final byte[] compress(final @Nullable byte[] input) {
- if(input==null) {
+ if(input==null)
return null;
- }
- if(input.length==0) {
+ if(input.length==0)
return input;
- }
try {
return _Bytes_GZipCompressorSmart.compress(input);
} catch (IOException e) {
@@ -302,12 +290,10 @@ public static final byte[] compress(final @Nullable byte[] input) {
* @return null if {@code compressed} is null
*/
public static final byte[] decompress(final @Nullable byte[] compressed) {
- if(compressed==null) {
+ if(compressed==null)
return null;
- }
- if(compressed.length==0) {
+ if(compressed.length==0)
return compressed;
- }
try {
return _Bytes_GZipCompressorSmart.decompress(compressed);
} catch (IOException e) {
@@ -323,12 +309,10 @@ public static final byte[] decompress(final @Nullable byte[] compressed) {
* @return null if {@code input} is null
*/
public static final byte[] compressZlib(final @Nullable byte[] input) {
- if(input==null) {
+ if(input==null)
return null;
- }
- if(input.length==0) {
+ if(input.length==0)
return input;
- }
try {
return _Bytes_ZLibCompressor.compress(input);
} catch (IOException e) {
@@ -344,12 +328,10 @@ public static final byte[] compressZlib(final @Nullable byte[] input) {
* @return null if {@code compressed} is null
*/
public static final byte[] decompressZlib(final @Nullable byte[] compressed) {
- if(compressed==null) {
+ if(compressed==null)
return null;
- }
- if(compressed.length==0) {
+ if(compressed.length==0)
return compressed;
- }
try {
return _Bytes_ZLibCompressor.decompress(compressed);
} catch (IOException e) {
@@ -427,6 +409,30 @@ public static BytesOperator operator() {
.andThen(bytes->decodeBase64(Base64.getDecoder(), bytes))
.andThen(_Bytes::decompress);
- // --
+
+ // -- EXTERNAL FORMAT
+
+ public static byte[] fromInts(final int[] ints) {
+ var bytes = new byte[ints.length];
+ IntStream.range(0, bytes.length)
+ .forEach(i->bytes[i] = (byte)ints[i]);
+ return bytes;
+ }
+
+ public static byte[] parse(final String asString) {
+ var ints = Stream.of(asString.split("\\,"))
+ .map(String::trim)
+ .mapToInt(Integer::parseInt)
+ .toArray();
+ return fromInts(ints);
+ }
+
+ public static String stringify(final byte[] bytes) {
+ var asString = IntStream.range(0, bytes.length)
+ .map(i->(int)bytes[i])
+ .mapToObj(i->""+i)
+ .collect(Collectors.joining(","));
+ return asString;
+ }
}
diff --git a/commons/src/test/java/org/apache/causeway/commons/internal/base/_BytesTest.java b/commons/src/test/java/org/apache/causeway/commons/internal/base/_BytesTest.java
new file mode 100644
index 00000000000..a3093bfa9d5
--- /dev/null
+++ b/commons/src/test/java/org/apache/causeway/commons/internal/base/_BytesTest.java
@@ -0,0 +1,40 @@
+/*
+ * Licensed to the Apache Software Foundation (ASF) under one
+ * or more contributor license agreements. See the NOTICE file
+ * distributed with this work for additional information
+ * regarding copyright ownership. The ASF licenses this file
+ * to you 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 org.apache.causeway.commons.internal.base;
+
+import java.security.NoSuchAlgorithmException;
+import java.security.SecureRandom;
+
+import org.junit.jupiter.api.Test;
+
+import static org.junit.jupiter.api.Assertions.assertEquals;
+
+class _BytesTest {
+
+ @Test
+ void stringifyRoundtrip() throws NoSuchAlgorithmException {
+ var secret = new byte[32];
+ SecureRandom.getInstanceStrong().nextBytes(secret);
+ var before = _Bytes.stringify(secret);
+ var after = _Bytes.stringify(_Bytes.parse(before));
+
+ assertEquals(before, after);
+ }
+
+}
diff --git a/core/runtimeservices/src/main/java/org/apache/causeway/core/runtimeservices/CausewayModuleCoreRuntimeServices.java b/core/runtimeservices/src/main/java/org/apache/causeway/core/runtimeservices/CausewayModuleCoreRuntimeServices.java
index 413d8bffdcc..24042c20ee0 100644
--- a/core/runtimeservices/src/main/java/org/apache/causeway/core/runtimeservices/CausewayModuleCoreRuntimeServices.java
+++ b/core/runtimeservices/src/main/java/org/apache/causeway/core/runtimeservices/CausewayModuleCoreRuntimeServices.java
@@ -18,6 +18,8 @@
*/
package org.apache.causeway.core.runtimeservices;
+import java.util.Optional;
+
import org.springframework.boot.autoconfigure.AutoConfigureOrder;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.context.annotation.Bean;
@@ -26,9 +28,12 @@
import org.springframework.context.annotation.Import;
import org.springframework.core.OrderComparator;
import org.springframework.core.annotation.AnnotationAwareOrderComparator;
+import org.springframework.util.Assert;
+import org.springframework.util.StringUtils;
import org.apache.causeway.applib.annotation.PriorityPrecedence;
import org.apache.causeway.applib.services.bookmark.HmacAuthority;
+import org.apache.causeway.commons.internal.base._Bytes;
import org.apache.causeway.core.codegen.bytebuddy.CausewayModuleCoreCodegenByteBuddy;
import org.apache.causeway.core.runtime.CausewayModuleCoreRuntime;
import org.apache.causeway.core.runtimeservices.bookmarks.BookmarkServiceDefault;
@@ -73,6 +78,8 @@
import org.apache.causeway.core.runtimeservices.xml.XmlServiceDefault;
import org.apache.causeway.core.runtimeservices.xmlsnapshot.XmlSnapshotServiceDefault;
+import lombok.extern.slf4j.Slf4j;
+
@Configuration(proxyBeanMethods = false)
@Import({
// Modules
@@ -132,6 +139,7 @@
})
@ComponentScan(basePackages = "org.apache.causeway.core.runtimeservices.icons")
+@Slf4j
public class CausewayModuleCoreRuntimeServices {
public static final String NAMESPACE = "causeway.runtimeservices";
@@ -147,7 +155,19 @@ static class HmacAuthorityAutoconfigure {
@Bean(NAMESPACE + ".fallbackHmacAuthority")
@ConditionalOnMissingBean(HmacAuthority.class)
public HmacAuthority fallbackHmacAuthority() {
- return HmacAuthority.HmacSHA256.randomInstance();
+
+ var secretFromEnv = Optional.ofNullable(System.getenv("causeway_hmac_sha256"))
+ .filter(StringUtils::hasText)
+ .map(_Bytes::parse)
+ .filter(secret->{
+ Assert.isTrue(secret.length>16, ()->"Illegal Argument: "
+ + "env. var. 'causeway_hmac_sha256' underflows required byte count of 16");
+ log.info("using secret from env. var. 'causeway_hmac_sha256' having length {} bytes", secret.length);
+ return true;
+ });
+
+ return secretFromEnv.map(HmacAuthority.HmacSHA256::new)
+ .orElseGet(HmacAuthority.HmacSHA256::randomInstance);
}
}