Sorry Justin, I should not have been so lazy with my typing.
I was referring to resolveServiceFromRequestContext which you mentioned in an 
earlier email.

Ray

On Wed, 2023-11-22 at 19:18 +0000, Justin Isenhour wrote:
Notice: This message was sent from outside the University of Victoria email 
system. Please be cautious with links and sensitive information.

The ForceAutn appears to be working as expected. In both use cases CAS is 
redirecting to the delegated IDP for authentication. In both cases the IDP is 
sending back to CAS and triggering the DelegatedClientAuthenticationAction. The 
very first time there is no existing TGT, so the Trasient Session Ticket get 
restored, which includes the requested service. After force auth/renew, when 
the action is triggered there is a TGT in scope, sonit is used.  I see no calls 
to WebUtils.putService to put the service back into the scope and no 
interactions to retrieve the service from the TST.  When I trace the calls for 
LDAP auth I see the same TGT is reused, it's not issuing a new ticket, so I 
think the TGT being is scope is fine. Seems like thrre should be a call 
somewhere to restore from TST.

I am not familiar with the method you mentioned but will review it once I am 
back in front of my computer.

Thanks,
Justin


________________________________
From: Ray Bon <[email protected]>
Sent: Wednesday, November 22, 2023 1:40:45 PM
To: [email protected] <[email protected]>
Cc: [email protected] <[email protected]>
Subject: Re: [cas-user] Re: CAS 5.3.16 loses service reference for SAML SP with 
ForcedAuth when CAS uses Delegated Auth

Justin,

Loggin out of the SP does not necessarily log out of cas (SLO is messy 
business).
If ForceAuthn is not forcing authentication, that should be your focus.
Perhaps cas is not sending ForceAuthn to the delegated authn server, or perhaps 
the delegated server is ignoring it.

Why does resolveServiceFromRequestContext return null?
Is the service removed from the context somewhere along the flow? Or perhaps 
resolve18Context is broken?

Ray

On Wed, 2023-11-22 at 09:34 -0800, Justin Isenhour wrote:
Notice: This message was sent from outside the University of Victoria email 
system. Please be cautious with links and sensitive information.

I've been tracing the code and have made the following observations:

The AuthNRequst from the SP comes into CAS and a TST is created with the 
service reference, then you are redirected to IDP.  After authenticating with 
IDP, you are redirected back to CAS, which triggers 
DelegatedClientAuthenticationAction.doExecute(final RequestContext 
context)<https://github.com/apereo/cas/blob/5.3.x/support/cas-server-support-pac4j-webflow/src/main/java/org/apereo/cas/web/flow/DelegatedClientAuthenticationAction.java#L184>.

For the first login, singleSignOnSessionExists(context) [L188] returns false.  
As a result of this, restoreAuthenticationRequestInContext(context, webContext, 
clientName) [L212] is called which restores the details from the TST, including 
the requested service reference.

After logout of SP and new AuthNRequest, singleSignOnSessionExists(context) 
[L188] returns true because the previously issued TGT is still in the webflow 
scope and the TGT is not expired.  Because of this, the code takes a different 
path, restoreAuthenticationRequestInContext is not called, instead the service 
is set by calling resolveServiceFromRequestContext(context) [L204], which 
returns null.

With debugging, I have seen that on the subsequent logins, if I have 
singleSignOnSessionExists return false like it does the first time through, it 
appears to work as desired and you land back in the client app every time.  
That points me to the TGT being in the webflow is causing this behavior.  
Thoughts?


DelegatedClientAuthenticationAction.doExecute code for reference:

    @Override
    public Event doExecute(final RequestContext context) {
        final HttpServletRequest request = 
WebUtils.getHttpServletRequestFromExternalWebflowContext(context);
        final HttpServletResponse response = 
WebUtils.getHttpServletResponseFromExternalWebflowContext(context);

        if (!isLogoutRequest(request) && singleSignOnSessionExists(context)) {
            final String tgt = WebUtils.getTicketGrantingTicketId(context);
            final Optional<Authentication> authnResult = 
getSingleSignOnAuthenticationFrom(context);

            if (authnResult.isPresent()) {
                final Authentication authentication = authnResult.get();
                final Object clientNames = authentication.getAttributes()
                    
.getOrDefault(ClientCredential.AUTHENTICATION_ATTRIBUTE_CLIENT_NAME, new 
ArrayList<>());
                final String clientName = 
CollectionUtils.firstElement(clientNames).map(Object::toString).orElse(StringUtils.EMPTY);
                final Service service = 
resolveServiceFromRequestContext(context);
                if (isDelegatedClientAuthorizedFor(clientName, service)) {
                    LOGGER.debug("An existing single sign-on session already 
exists. Skipping delegation and routing back to CAS authentication flow");
                    prepareForLoginPage(context);
                    return resumeWebflow();
                }
            }
            final Service resolvedService = 
resolveServiceFromRequestContext(context);
            LOGGER.debug("Single sign-on session in unauthorized for service 
[{}]", resolvedService);
            centralAuthenticationService.deleteTicket(tgt);
        }

        final String clientName = 
request.getParameter(Pac4jConstants.DEFAULT_CLIENT_NAME_PARAMETER);
        LOGGER.debug("Delegated authentication is handled by client name [{}]", 
clientName);
        if (hasDelegationRequestFailed(request, 
response.getStatus()).isPresent()) {
            throw new IllegalArgumentException("Delegated authentication has 
failed with client " + clientName);
        }

        final String logoutEndpoint = 
request.getParameter(SAML2ServiceProviderMetadataResolver.LOGOUT_ENDPOINT_PARAMETER);
        final J2EContext webContext = Pac4jUtils.getPac4jJ2EContext(request, 
response);
        if (StringUtils.isNotBlank(clientName)) {
            final Service service;
            if (StringUtils.isBlank(logoutEndpoint)) {
                service = restoreAuthenticationRequestInContext(context, 
webContext, clientName);
            } else {
                service = null;
            }
            final BaseClient<Credentials, CommonProfile> client = 
findDelegatedClientByName(request, clientName, service);

            final Credentials credentials;
            try {
                credentials = client.getCredentials(webContext);
                LOGGER.debug("Retrieved credentials from client as [{}]", 
credentials);
                if (credentials == null) {
                    throw new IllegalArgumentException("Unable to determine 
credentials from the context with client " + client.getName());
                }
            } catch (final Exception e) {
                return handleException(webContext, client, e);
            }

            final ClientCredential clientCredential = new 
ClientCredential(credentials, client.getName());
            WebUtils.putCredential(context, clientCredential);
            WebUtils.putService(context, service);
            final Service resolvedService = 
authenticationRequestServiceSelectionStrategies.resolveService(service);
            final RegisteredService registeredService = 
servicesManager.findServiceBy(resolvedService);
            WebUtils.putRegisteredService(context, registeredService);
            return super.doExecute(context);
        }

        prepareForLoginPage(context);

        if (response.getStatus() == HttpStatus.UNAUTHORIZED.value()) {
            return stopWebflow();
        }
        return error();
    }

On Wednesday, November 22, 2023 at 12:11:38 AM UTC-5 Justin Isenhour wrote:
Hello,

I'm hoping someone may have a suggestion of where I can look for the root of 
this problem.

We are running CAS 5.3.16 and have a mix of authentication handlers setup 
including several LDAP auth handlers, delegated auth to AzureAD via OIDC, and 
SAML delegated auth to various other IDPs.  We have a SAML client that is 
sending an AuthNRequest with ForceAuthn="true" that is not working as expected 
when CAS uses Delegated auth.

On the first login request, everything seems to be working fine.  If you log 
out of that client application, then login again, you get prompted for 
authentication as expected, but instead of being redirected back to the 
requested client, CAS directs to the the generic success page.

This is only an issue when authentication is done via delegated authentication 
client, saml and oidc but have the same issue.  If authentication is done 
directly in CAS via LDAP auth handler, then the flow works as expected and you 
land back into the app every time.

I have CAS source code and am pretty familiar with the code, we been using CAS 
since 3.x, but I haven't been able to pin point the issue yet.  Anyone have any 
advice or suggestions?

Thanks in advance,
Justin Isenhour





-- 
- Website: https://apereo.github.io/cas
- Gitter Chatroom: https://gitter.im/apereo/cas
- List Guidelines: https://goo.gl/1VRrw7
- Contributions: https://goo.gl/mh7qDG
--- 
You received this message because you are subscribed to the Google Groups "CAS 
Community" group.
To unsubscribe from this group and stop receiving emails from it, send an email 
to [email protected].
To view this discussion on the web visit 
https://groups.google.com/a/apereo.org/d/msgid/cas-user/435544b199f5641e0c7314db1c12eaae18906fa8.camel%40uvic.ca.

Reply via email to