class CasCredentialsExtractor implements CredentialsExtractor {

    private final static int DECOMPRESSION_FACTOR = 10;

    protected CasConfiguration configuration;

    /**
     * <p>Constructor for CasCredentialsExtractor.</p>
     *
     * @param configuration a {@link CasConfiguration} object
     */
    public CasCredentialsExtractor(final CasConfiguration configuration) {
        CommonHelper.assertNotNull("configuration", configuration);
        this.configuration = configuration;
    }

    /** {@inheritDoc} */
    @Override
    public Optional<Credentials> extract(final CallContext ctx) {<FILL_FUNCTION_BODY>}

    /**
     * <p>isTokenRequest.</p>
     *
     * @param context a {@link WebContext} object
     * @return a boolean
     */
    protected boolean isTokenRequest(final WebContext context) {
        return getArtifactParameter(context).isPresent();
    }

    /**
     * <p>getArtifactParameter.</p>
     *
     * @param context a {@link WebContext} object
     * @return a {@link Optional} object
     */
    protected Optional<String> getArtifactParameter(final WebContext context) {
        if (configuration.getProtocol() == CasProtocol.SAML) {
            val optValue = context.getRequestParameter(Protocol.SAML11.getArtifactParameterName());
            if (optValue.isPresent()) {
                return optValue;
            }
        }
        return context.getRequestParameter(CasConfiguration.TICKET_PARAMETER);
    }

    /**
     * <p>isBackLogoutRequest.</p>
     *
     * @param context a {@link WebContext} object
     * @return a boolean
     */
    protected boolean isBackLogoutRequest(final WebContext context) {
        return WebContextHelper.isPost(context)
                && !isMultipartRequest(context)
                && context.getRequestParameter(CasConfiguration.LOGOUT_REQUEST_PARAMETER).isPresent();
    }

    /**
     * <p>isMultipartRequest.</p>
     *
     * @param context a {@link WebContext} object
     * @return a boolean
     */
    protected boolean isMultipartRequest(final WebContext context) {
        val contentType = context.getRequestHeader(HttpConstants.CONTENT_TYPE_HEADER);
        return contentType.isPresent() && contentType.get().toLowerCase().startsWith("multipart");
    }

    /**
     * <p>isFrontLogoutRequest.</p>
     *
     * @param context a {@link WebContext} object
     * @return a boolean
     */
    protected boolean isFrontLogoutRequest(final WebContext context) {
        return WebContextHelper.isGet(context)
                && context.getRequestParameter(CasConfiguration.LOGOUT_REQUEST_PARAMETER).isPresent();
    }

    /**
     * <p>uncompressLogoutMessage.</p>
     *
     * @param originalMessage a {@link String} object
     * @return a {@link String} object
     */
    protected String uncompressLogoutMessage(final String originalMessage) {
        val binaryMessage = Base64.getMimeDecoder().decode(originalMessage);

        Inflater decompresser = null;
        try {
            // decompress the bytes
            decompresser = new Inflater();
            decompresser.setInput(binaryMessage);
            val result = new byte[binaryMessage.length * DECOMPRESSION_FACTOR];

            val resultLength = decompresser.inflate(result);

            // decode the bytes into a String
            return new String(result, 0, resultLength, "UTF-8");
        } catch (final Exception e) {
            LOGGER.error("Unable to decompress logout message", e);
            throw new TechnicalException(e);
        } finally {
            if (decompresser != null) {
                decompresser.end();
            }
        }
    }
}

        Credentials credentials = null;

        val webContext = ctx.webContext();

        // like the SingleSignOutFilter from the Apereo CAS client:
        if (isTokenRequest(webContext)) {
            val ticket = getArtifactParameter(webContext).get();
            credentials = new TokenCredentials(ticket);

        } else if (isBackLogoutRequest(webContext)) {
            val logoutMessage = webContext.getRequestParameter(CasConfiguration.LOGOUT_REQUEST_PARAMETER).get();
            LOGGER.trace("Logout request:\n{}", logoutMessage);

            val ticket = XmlUtils.getTextForElement(logoutMessage, CasConfiguration.SESSION_INDEX_TAG);
            credentials = new SessionKeyCredentials(LogoutType.BACK, ticket);

        } else if (isFrontLogoutRequest(webContext)) {
            val logoutMessage = uncompressLogoutMessage(
                webContext.getRequestParameter(CasConfiguration.LOGOUT_REQUEST_PARAMETER).get());
            LOGGER.trace("Logout request:\n{}", logoutMessage);

            val ticket = XmlUtils.getTextForElement(logoutMessage, CasConfiguration.SESSION_INDEX_TAG);
            credentials = new SessionKeyCredentials(LogoutType.FRONT, ticket);
        }

        LOGGER.debug("extracted credentials: {}", credentials);
        return Optional.ofNullable(credentials);
    
<no_super_class>