net.shibboleth.idp.saml.audit.impl.WriteFTICKSLog.java Source code

Java tutorial

Introduction

Here is the source code for net.shibboleth.idp.saml.audit.impl.WriteFTICKSLog.java

Source

/*
 * Licensed to the University Corporation for Advanced Internet Development, 
 * Inc. (UCAID) under one or more contributor license agreements.  See the 
 * NOTICE file distributed with this work for additional information regarding
 * copyright ownership. The UCAID 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 net.shibboleth.idp.saml.audit.impl;

import java.security.NoSuchAlgorithmException;

import javax.annotation.Nonnull;
import javax.annotation.Nullable;

import net.shibboleth.idp.authn.context.SubjectContext;
import net.shibboleth.idp.authn.context.navigate.SubjectContextPrincipalLookupFunction;
import net.shibboleth.idp.profile.AbstractProfileAction;
import net.shibboleth.idp.profile.context.AuditContext;
import net.shibboleth.idp.profile.context.navigate.RelyingPartyIdLookupFunction;
import net.shibboleth.idp.profile.context.navigate.ResponderIdLookupFunction;
import net.shibboleth.utilities.java.support.annotation.constraint.NonnullAfterInit;
import net.shibboleth.utilities.java.support.annotation.constraint.NotEmpty;
import net.shibboleth.utilities.java.support.codec.StringDigester;
import net.shibboleth.utilities.java.support.component.ComponentInitializationException;
import net.shibboleth.utilities.java.support.component.ComponentSupport;
import net.shibboleth.utilities.java.support.logic.Constraint;
import net.shibboleth.utilities.java.support.primitive.StringSupport;

import org.opensaml.messaging.context.navigate.ChildContextLookup;
import org.opensaml.messaging.context.navigate.MessageLookup;
import org.opensaml.profile.context.ProfileRequestContext;
import org.opensaml.profile.context.navigate.OutboundMessageContextLookup;
import org.opensaml.saml.common.SAMLObject;
import org.slf4j.LoggerFactory;

import com.google.common.base.Function;
import com.google.common.base.Functions;

/**
 * Action that produces F-TICKS log entries for successful SAML SSO responses. 
 * 
 * @event {@link org.opensaml.profile.action.EventIds#PROCEED_EVENT_ID}
 */
public class WriteFTICKSLog extends AbstractProfileAction {

    /** Logging category to use. */
    @Nonnull
    @NotEmpty
    public static final String FTICKS_LOG_CATEGORY = "Shibboleth-FTICKS";

    /** Strategy used to locate the {@link AuditContext} associated with a given {@link ProfileRequestContext}. */
    @Nonnull
    private Function<ProfileRequestContext, AuditContext> auditContextLookupStrategy;

    /** Federation ID for log. */
    @NonnullAfterInit
    @NotEmpty
    private String federationId;

    /** Digest algorithm for username hashing. */
    @Nonnull
    @NotEmpty
    private String digestAlgorithm;

    /** Salt for username hashing. */
    @Nullable
    private String salt;

    /** Lookup strategy for relying party ID. */
    @Nonnull
    private Function<ProfileRequestContext, String> relyingPartyLookupStrategy;

    /** Lookup strategy for responder ID. */
    @Nonnull
    private Function<ProfileRequestContext, String> responderLookupStrategy;

    /** Lookup strategy for username. */
    @Nonnull
    private Function<ProfileRequestContext, String> usernameLookupStrategy;

    /** Lookup strategy for authentication method. */
    @Nonnull
    private Function<ProfileRequestContext, String> authenticationMethodLookupStrategy;

    /** Lookup strategy for StatusCode. */
    @Nonnull
    private Function<ProfileRequestContext, String> statusCodeLookupStrategy;

    /** Username hasher. */
    @NonnullAfterInit
    private StringDigester digester;

    /** Constructor. */
    public WriteFTICKSLog() {
        relyingPartyLookupStrategy = new RelyingPartyIdLookupFunction();
        responderLookupStrategy = new ResponderIdLookupFunction();
        usernameLookupStrategy = Functions.compose(new SubjectContextPrincipalLookupFunction(),
                new ChildContextLookup(SubjectContext.class));
        authenticationMethodLookupStrategy = new AuthnContextAuditExtractor(
                Functions.compose(new MessageLookup(SAMLObject.class), new OutboundMessageContextLookup()));
        statusCodeLookupStrategy = new StatusCodeAuditExtractor(
                Functions.compose(new MessageLookup(SAMLObject.class), new OutboundMessageContextLookup()));
    }

    /**
     * Set the federation identifier for the log.
     * 
     * @param id federation identifier
     */
    public void setFederationId(@Nonnull @NotEmpty final String id) {
        federationId = Constraint.isNotNull(StringSupport.trimOrNull(id), "Federation ID cannot be null or empty");
    }

    /**
     * Set the digest algorithm for username hashing.
     * 
     * @param alg digest algorithm
     */
    public void setDigestAlgorithm(@Nonnull @NotEmpty final String alg) {
        digestAlgorithm = Constraint.isNotNull(StringSupport.trimOrNull(alg),
                "Digest algorithm cannot be null or empty");
    }

    /**
     * Set the salt for username hashing.
     * 
     * @param s salt
     */
    public void setSalt(@Nullable final String s) {
        if (s != null && !s.isEmpty()) {
            salt = s;
        } else {
            salt = null;
        }
    }

    /**
     * Set the strategy used to locate the relying party ID.
     * 
     * @param strategy lookup strategy
     */
    public void setRelyingPartyLookupStrategy(@Nonnull final Function<ProfileRequestContext, String> strategy) {
        ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);

        relyingPartyLookupStrategy = Constraint.isNotNull(strategy,
                "Relying Party ID lookup strategy cannot be null");
    }

    /**
     * Set the strategy used to locate the responder ID.
     * 
     * @param strategy lookup strategy
     */
    public void setResponderLookupStrategy(@Nonnull final Function<ProfileRequestContext, String> strategy) {
        ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);

        responderLookupStrategy = Constraint.isNotNull(strategy, "Responder ID lookup strategy cannot be null");
    }

    /**
     * Set the strategy used to locate the username.
     * 
     * @param strategy lookup strategy
     */
    public void setUsernameLookupStrategy(@Nonnull final Function<ProfileRequestContext, String> strategy) {
        ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);

        usernameLookupStrategy = Constraint.isNotNull(strategy, "Username lookup strategy cannot be null");
    }

    /**
     * Set the strategy used to locate the authentication method.
     * 
     * @param strategy lookup strategy
     */
    public void setAuthenticationMethodLookupStrategy(
            @Nonnull final Function<ProfileRequestContext, String> strategy) {
        ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);

        authenticationMethodLookupStrategy = Constraint.isNotNull(strategy,
                "Authentication method lookup strategy cannot be null");
    }

    /**
     * Set the strategy used to locate the status code.
     * 
     * @param strategy lookup strategy
     */
    public void setStatusCodeLookupStrategy(@Nonnull final Function<ProfileRequestContext, String> strategy) {
        ComponentSupport.ifInitializedThrowUnmodifiabledComponentException(this);

        statusCodeLookupStrategy = Constraint.isNotNull(strategy, "StatusCode lookup strategy cannot be null");
    }

    /** {@inheritDoc} */
    @Override
    protected void doInitialize() throws ComponentInitializationException {
        super.doInitialize();

        if (federationId == null) {
            throw new ComponentInitializationException("Federation ID cannot be null or empty.");
        }

        try {
            digester = new StringDigester(digestAlgorithm, StringDigester.OutputFormat.HEX_LOWER);
            digester.setSalt(salt);
            digester.setRequireSalt(true);
        } catch (final NoSuchAlgorithmException e) {
            throw new ComponentInitializationException(e);
        }

    }

    // Checkstyle: CyclomaticComplexity OFF
    /** {@inheritDoc} */
    @Override
    protected void doExecute(@Nonnull final ProfileRequestContext profileRequestContext) {

        //"F-TICKS/%{idp.fticks.federation:Undefined}/1.0#TS=%T#RP=%SP#AP=%IDP #PN=%HASHEDu#AM=%ac#"

        final StringBuilder record = new StringBuilder("F-TICKS/");
        record.append(federationId).append("/1.0#TS=").append(System.currentTimeMillis() / 1000);

        String field = relyingPartyLookupStrategy.apply(profileRequestContext);
        if (field != null && !field.isEmpty()) {
            record.append("#RP=").append(field);
        }

        field = responderLookupStrategy.apply(profileRequestContext);
        if (field != null && !field.isEmpty()) {
            record.append("#AP=").append(field);
        }

        field = usernameLookupStrategy.apply(profileRequestContext);
        if (field != null && !field.isEmpty()) {
            field = digester.apply(field);
            if (field != null && !field.isEmpty()) {
                record.append("#PN=").append(field);
            }
        }

        field = authenticationMethodLookupStrategy.apply(profileRequestContext);
        if (field != null && !field.isEmpty()) {
            record.append("#AM=").append(field);
        }

        field = statusCodeLookupStrategy.apply(profileRequestContext);
        if (field != null && (org.opensaml.saml.saml1.core.StatusCode.SUCCESS.getLocalPart().equals(field)
                || org.opensaml.saml.saml2.core.StatusCode.SUCCESS.equals(field))) {
            record.append("#RESULT=OK");
        } else {
            record.append("#RESULT=FAIL");
        }

        record.append("#");
        LoggerFactory.getLogger(FTICKS_LOG_CATEGORY).info(record.toString());
    }
    // Checkstyle: CyclomaticComplexity ON

}