PrincipalManagerUtil.java
/*
* Copyright (c) 2007-2026 MetaSolutions AB
*
* 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 org.entrystore.rest.springboot.util;
import lombok.AccessLevel;
import lombok.NoArgsConstructor;
import org.entrystore.PrincipalManager;
import java.net.URI;
import java.util.function.Function;
import java.util.function.Supplier;
@NoArgsConstructor(access = AccessLevel.PRIVATE)
public final class PrincipalManagerUtil {
/**
* Runs {@code action} with the per-thread authenticated user elevated to the admin user, restoring
* the previous user in all cases (including when {@code action} throws) via
* {@link #restoreAuthenticatedUserSafely(PrincipalManager, URI, Throwable)}.
* <p>
* The action receives the pre-elevation user URI — inside the action,
* {@code pm.getAuthenticatedUserURI()} returns the admin URI, so use the parameter whenever the
* body needs the requesting user (e.g. to set a creator or grant ACLs).
*/
public static <T> T runAsAdmin(PrincipalManager pm, Function<URI, T> action) {
URI previous = pm.getAuthenticatedUserURI();
Throwable primary = null;
try {
pm.setAuthenticatedUserURI(pm.getAdminUser().getURI());
return action.apply(previous);
} catch (Throwable t) {
primary = t;
throw t;
} finally {
restoreAuthenticatedUserSafely(pm, previous, primary);
}
}
/**
* Runs {@code action} as the admin user; see {@link #runAsAdmin(PrincipalManager, Function)}.
* Use the {@link Function} overload instead when the action's body needs the pre-elevation user
* URI — inside the action, {@code pm.getAuthenticatedUserURI()} returns the admin URI.
*/
public static <T> T runAsAdmin(PrincipalManager pm, Supplier<T> action) {
return runAsAdmin(pm, _ -> action.get());
}
/**
* Runs {@code action} as the admin user; see {@link #runAsAdmin(PrincipalManager, Function)}.
*/
public static void runAsAdmin(PrincipalManager pm, Runnable action) {
runAsAdmin(pm, () -> {
action.run();
return null;
});
}
/**
* Restore the per-thread authenticated user URI to {@code previous} from inside a {@code finally}
* block, without masking an exception thrown by the surrounding {@code try} body.
* <p>
* Intended usage — the caller captures any try-body exception via precise rethrow and passes it
* as {@code primary}:
* <pre>{@code
* URI previous = pm.getAuthenticatedUserURI();
* Throwable primary = null;
* try {
* // body
* } catch (Throwable t) {
* primary = t;
* throw t;
* } finally {
* PrincipalManagerUtil.restoreAuthenticatedUserSafely(pm, previous, primary);
* }
* }</pre>
* <p>
* If the restore call throws:
* <ul>
* <li>when {@code primary != null}, the cleanup failure is attached to {@code primary} via
* {@link Throwable#addSuppressed(Throwable)} so the original try-body exception still
* propagates and the cleanup failure is preserved in the same error event;</li>
* <li>when {@code primary == null}, the cleanup failure propagates unchanged.</li>
* </ul>
*/
public static void restoreAuthenticatedUserSafely(PrincipalManager pm, URI previous, Throwable primary) {
try {
pm.setAuthenticatedUserURI(previous);
} catch (Throwable cleanupEx) {
if (primary != null) {
primary.addSuppressed(cleanupEx);
} else {
throw cleanupEx;
}
}
}
}