Skip to content

Commit 53e5066

Browse files
authored
Merge pull request #59 from karelmaxa/fix-security-authenticationid
Resolve principal name from request context map
2 parents 2a0c68b + 219531f commit 53e5066

File tree

2 files changed

+84
-9
lines changed

2 files changed

+84
-9
lines changed

auth-filters/forgerock-authn-filter/forgerock-jaspi-runtime/src/main/java/org/forgerock/caf/authentication/framework/AuthenticationFramework.java

+24-8
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@
1212
* information: "Portions copyright [year] [name of copyright owner]".
1313
*
1414
* Copyright 2013-2016 ForgeRock AS.
15+
* Portions Copyright 2025 Wren Security.
1516
*/
1617

1718
package org.forgerock.caf.authentication.framework;
@@ -29,12 +30,14 @@
2930
import java.util.HashMap;
3031
import java.util.HashSet;
3132
import java.util.Map;
33+
import java.util.Objects;
3234
import javax.security.auth.Subject;
3335
import javax.security.auth.message.AuthStatus;
3436

3537
import org.forgerock.caf.authentication.api.AsyncServerAuthContext;
3638
import org.forgerock.caf.authentication.api.AuthenticationException;
3739
import org.forgerock.caf.authentication.api.MessageContext;
40+
import org.forgerock.caf.authentication.api.MessageInfoContext;
3841
import org.forgerock.http.Handler;
3942
import org.forgerock.http.protocol.Request;
4043
import org.forgerock.http.protocol.Response;
@@ -86,7 +89,6 @@ public final class AuthenticationFramework {
8689
*/
8790
public static final String ATTRIBUTE_REQUEST_ID = "org.forgerock.authentication.request.id";
8891

89-
@SuppressWarnings("unchecked")
9092
static final Collection<Class<?>> REQUIRED_MESSAGE_TYPES_SUPPORT =
9193
new HashSet<Class<?>>(Arrays.asList(Request.class, Response.class));
9294

@@ -161,13 +163,7 @@ public Promise<AuthStatus, AuthenticationException> apply(AuthStatus authStatus)
161163
AuthenticationException exception = new AuthenticationFailedException();
162164
return Promises.newExceptionPromise(exception);
163165
} else if (isSuccess(authStatus)) {
164-
String principalName = null;
165-
for (Principal principal : clientSubject.getPrincipals()) {
166-
if (principal.getName() != null) {
167-
principalName = principal.getName();
168-
break;
169-
}
170-
}
166+
String principalName = resolvePrincipalName(context, clientSubject);
171167
Map<String, Object> contextMap =
172168
getMap(context.getRequestContextMap(), AuthenticationFramework.ATTRIBUTE_AUTH_CONTEXT);
173169
logger.debug("Setting principal name, {}, and {} context values on to the request.",
@@ -252,6 +248,26 @@ private Map<String, Object> getMap(Map<String, Object> containingMap, String key
252248
return map;
253249
}
254250

251+
/**
252+
* Resolve the principal name of the user. The principal name is resolved from the context map
253+
* of the {@link MessageInfoContext}. When no value is specified in the context map, the principal name
254+
* is resolved from the client subject principal.
255+
* @param context Request context.
256+
* @param clientSubject Client subject.
257+
* @return Resolved principal name or null if no value could be resolved.
258+
*/
259+
private String resolvePrincipalName(final MessageContext context, final Subject clientSubject) {
260+
String principal = (String) context.getRequestContextMap().get(ATTRIBUTE_AUTH_PRINCIPAL);
261+
if (principal != null) {
262+
return principal;
263+
}
264+
return clientSubject.getPrincipals().stream()
265+
.map(Principal::getName)
266+
.filter(Objects::nonNull)
267+
.findFirst()
268+
.orElse(null);
269+
}
270+
255271
@Override
256272
public String toString() {
257273
return "AuthContext: " + authContext.toString() + ", Response Handlers: " + responseHandler.toString();

auth-filters/forgerock-authn-filter/forgerock-jaspi-runtime/src/test/java/org/forgerock/caf/authentication/framework/AuthenticationFrameworkTest.java

+60-1
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,7 @@
1212
* information: "Portions copyright [year] [name of copyright owner]".
1313
*
1414
* Copyright 2013-2016 ForgeRock AS.
15-
* Portions Copyright 2018 Wren Security.
15+
* Portions Copyright 2018-2025 Wren Security.
1616
*/
1717

1818
package org.forgerock.caf.authentication.framework;
@@ -24,7 +24,9 @@
2424
import static org.mockito.Mockito.mock;
2525
import static org.mockito.Mockito.never;
2626
import static org.mockito.Mockito.verify;
27+
import static org.testng.Assert.assertEquals;
2728

29+
import java.security.Principal;
2830
import javax.security.auth.Subject;
2931
import javax.security.auth.message.AuthStatus;
3032

@@ -44,6 +46,8 @@
4446
import org.forgerock.util.promise.NeverThrowsException;
4547
import org.forgerock.util.promise.Promise;
4648
import org.forgerock.util.promise.Promises;
49+
import org.mockito.invocation.InvocationOnMock;
50+
import org.mockito.stubbing.Answer;
4751
import org.slf4j.Logger;
4852
import org.testng.annotations.BeforeMethod;
4953
import org.testng.annotations.DataProvider;
@@ -349,4 +353,59 @@ public void whenResourceReturnsResponseExceptionItShouldBeSecuredAndReturned() t
349353
verify(authContext).secureResponse(any(MessageContext.class), eq(serviceSubject));
350354
verify(authContext).cleanSubject(any(MessageContext.class), any(Subject.class));
351355
}
356+
357+
@Test
358+
public void whenProcessingResultShouldSetPrincipalFromMessageContext() {
359+
String principal = "john.doe";
360+
Context context = mockContext();
361+
Request request = new Request();
362+
Handler next = mockHandler(request, Promises.<Response, NeverThrowsException>newResultPromise(successfulResponse));
363+
mockAuthContext(Promises.<AuthStatus, AuthenticationException>newResultPromise(AuthStatus.SUCCESS),
364+
Promises.<AuthStatus, AuthenticationException>newResultPromise(AuthStatus.SEND_SUCCESS));
365+
given(authContext.validateRequest(any(MessageContext.class), any(Subject.class), eq(serviceSubject))).willAnswer(new Answer<Object>() {
366+
@Override
367+
public Object answer(InvocationOnMock invocation) throws Throwable {
368+
MessageContext context = (MessageContext) invocation.getArgument(0);
369+
context.getRequestContextMap().put(AuthenticationFramework.ATTRIBUTE_AUTH_PRINCIPAL, principal);
370+
return Promises.<AuthStatus, AuthenticationException>newResultPromise(AuthStatus.SUCCESS);
371+
}
372+
});
373+
374+
Promise<Response, NeverThrowsException> promise = runtime.processMessage(context, request, next);
375+
376+
assertThat(promise).succeeded();
377+
AttributesContext attributesContext = context.asContext(AttributesContext.class);
378+
assertEquals(attributesContext.getAttributes().get(AuthenticationFramework.ATTRIBUTE_AUTH_PRINCIPAL), principal);
379+
}
380+
381+
@Test
382+
public void whenProcessingResultShouldSetPrincipalFromClientSubject() {
383+
String principal = "john.doe";
384+
Context context = mockContext();
385+
Request request = new Request();
386+
Handler next = mockHandler(request, Promises.<Response, NeverThrowsException>newResultPromise(successfulResponse));
387+
mockAuthContext(Promises.<AuthStatus, AuthenticationException>newResultPromise(AuthStatus.SUCCESS),
388+
Promises.<AuthStatus, AuthenticationException>newResultPromise(AuthStatus.SEND_SUCCESS));
389+
given(authContext.validateRequest(any(MessageContext.class), any(Subject.class), eq(serviceSubject))).willAnswer(new Answer<Object>() {
390+
@Override
391+
public Object answer(InvocationOnMock invocation) throws Throwable {
392+
Subject subject = (Subject) invocation.getArgument(1);
393+
subject.getPrincipals().add(new Principal() {
394+
@Override
395+
public String getName() {
396+
return principal;
397+
}
398+
399+
});
400+
return Promises.<AuthStatus, AuthenticationException>newResultPromise(AuthStatus.SUCCESS);
401+
}
402+
});
403+
404+
Promise<Response, NeverThrowsException> promise = runtime.processMessage(context, request, next);
405+
406+
assertThat(promise).succeeded();
407+
AttributesContext attributesContext = context.asContext(AttributesContext.class);
408+
assertEquals(attributesContext.getAttributes().get(AuthenticationFramework.ATTRIBUTE_AUTH_PRINCIPAL), principal);
409+
}
410+
352411
}

0 commit comments

Comments
 (0)