org.taverna.server.master.identity.WorkflowInternalAuthProvider.java Source code

Java tutorial

Introduction

Here is the source code for org.taverna.server.master.identity.WorkflowInternalAuthProvider.java

Source

/*
 */
package org.taverna.server.master.identity;
/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF 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.
 */

import static java.util.Collections.synchronizedMap;
import static org.springframework.web.context.request.RequestContextHolder.currentRequestAttributes;
import static org.taverna.server.master.common.Roles.SELF;

import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.Arrays;
import java.util.HashSet;
import java.util.LinkedHashMap;
import java.util.Map;
import java.util.Set;

import javax.annotation.Nonnull;
import javax.annotation.PostConstruct;
import javax.annotation.PreDestroy;
import javax.servlet.http.HttpServletRequest;

import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.springframework.beans.factory.annotation.Required;
import org.springframework.security.authentication.AuthenticationServiceException;
import org.springframework.security.authentication.BadCredentialsException;
import org.springframework.security.authentication.UsernamePasswordAuthenticationToken;
import org.springframework.security.authentication.dao.AbstractUserDetailsAuthenticationProvider;
import org.springframework.security.core.Authentication;
import org.springframework.security.core.AuthenticationException;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.context.SecurityContextHolder;
import org.springframework.security.core.userdetails.User;
import org.springframework.security.core.userdetails.UserDetails;
import org.springframework.security.core.userdetails.UsernameNotFoundException;
import org.springframework.security.web.authentication.WebAuthenticationDetails;
import org.springframework.web.context.request.ServletRequestAttributes;
import org.taverna.server.master.exceptions.UnknownRunException;
import org.taverna.server.master.interfaces.LocalIdentityMapper;
import org.taverna.server.master.interfaces.RunStore;
import org.taverna.server.master.utils.CallTimeLogger.PerfLogged;
import org.taverna.server.master.utils.UsernamePrincipal;
import org.taverna.server.master.worker.RunDatabaseDAO;

/**
 * A special authentication provider that allows a workflow to authenticate to
 * itself. This is used to allow the workflow to publish to its own interaction
 * feed.
 * 
 * @author Donal Fellows
 */
public class WorkflowInternalAuthProvider extends AbstractUserDetailsAuthenticationProvider {
    private Log log = LogFactory.getLog("Taverna.Server.UserDB");
    private static final boolean logDecisions = true;
    public static final String PREFIX = "wfrun_";
    private RunDatabaseDAO dao;
    private Map<String, String> cache;

    @Required
    public void setDao(RunDatabaseDAO dao) {
        this.dao = dao;
    }

    @Required
    @SuppressWarnings("serial")
    public void setCacheBound(final int bound) {
        cache = synchronizedMap(new LinkedHashMap<String, String>() {
            @Override
            protected boolean removeEldestEntry(Map.Entry<String, String> eldest) {
                return size() > bound;
            }
        });
    }

    public void setAuthorizedAddresses(String[] addresses) {
        authorizedAddresses = new HashSet<>(localAddresses);
        for (String s : addresses)
            authorizedAddresses.add(s);
    }

    @PostConstruct
    public void logConfig() {
        log.info("authorized addresses for automatic access: " + authorizedAddresses);
    }

    @PreDestroy
    void closeLog() {
        log = null;
    }

    private final Set<String> localAddresses = new HashSet<>();
    private Set<String> authorizedAddresses;
    {
        localAddresses.add("127.0.0.1"); // IPv4
        localAddresses.add("::1"); // IPv6
        try {
            InetAddress addr = InetAddress.getLocalHost();
            if (!addr.isLoopbackAddress())
                localAddresses.add(addr.getHostAddress());
        } catch (UnknownHostException e) {
            // Ignore the exception
        }
        authorizedAddresses = new HashSet<>(localAddresses);
    }

    /**
     * Check that the authentication request is actually valid for the given
     * user record.
     * 
     * @param userRecord
     *            as retrieved from the
     *            {@link #retrieveUser(String, UsernamePasswordAuthenticationToken)}
     *            or <code>UserCache</code>
     * @param principal
     *            the principal that is trying to authenticate (and that we're
     *            trying to bind)
     * @param credentials
     *            the credentials (e.g., password) presented by the principal
     * 
     * @throws AuthenticationException
     *             AuthenticationException if the credentials could not be
     *             validated (generally a <code>BadCredentialsException</code>,
     *             an <code>AuthenticationServiceException</code>)
     * @throws Exception
     *             If something goes wrong. Will be logged and converted to a
     *             generic AuthenticationException.
     */
    protected void additionalAuthenticationChecks(UserDetails userRecord, @Nonnull Object principal,
            @Nonnull Object credentials) throws Exception {
        @Nonnull
        HttpServletRequest req = ((ServletRequestAttributes) currentRequestAttributes()).getRequest();

        // Are we coming from a "local" address?
        if (!req.getLocalAddr().equals(req.getRemoteAddr()) && !authorizedAddresses.contains(req.getRemoteAddr())) {
            if (logDecisions)
                log.info("attempt to use workflow magic token from untrusted address:" + " token="
                        + userRecord.getUsername() + ", address=" + req.getRemoteAddr());
            throw new BadCredentialsException("bad login token");
        }

        // Does the password match?
        if (!credentials.equals(userRecord.getPassword())) {
            if (logDecisions)
                log.info("workflow magic token is untrusted due to password mismatch:" + " wanted="
                        + userRecord.getPassword() + ", got=" + credentials);
            throw new BadCredentialsException("bad login token");
        }

        if (logDecisions)
            log.info("granted role " + SELF + " to user " + userRecord.getUsername());
    }

    /**
     * Retrieve the <code>UserDetails</code> from the relevant store, with the
     * option of throwing an <code>AuthenticationException</code> immediately if
     * the presented credentials are incorrect (this is especially useful if it
     * is necessary to bind to a resource as the user in order to obtain or
     * generate a <code>UserDetails</code>).
     * 
     * @param username
     *            The username to retrieve
     * @param details
     *            The details from the authentication request.
     * @see #retrieveUser(String,UsernamePasswordAuthenticationToken)
     * @return the user information (never <code>null</code> - instead an
     *         exception should the thrown)
     * @throws AuthenticationException
     *             if the credentials could not be validated (generally a
     *             <code>BadCredentialsException</code>, an
     *             <code>AuthenticationServiceException</code> or
     *             <code>UsernameNotFoundException</code>)
     * @throws Exception
     *             If something goes wrong. It will be logged and converted into
     *             a general AuthenticationException.
     */
    @Nonnull
    protected UserDetails retrieveUser(String username, Object details) throws Exception {
        if (details == null || !(details instanceof WebAuthenticationDetails))
            throw new UsernameNotFoundException("context unsupported");
        if (!username.startsWith(PREFIX))
            throw new UsernameNotFoundException("unsupported username for this provider");
        if (logDecisions)
            log.info("request for auth for user " + username);
        String wfid = username.substring(PREFIX.length());
        String securityToken;
        try {
            securityToken = cache.get(wfid);
            if (securityToken == null) {
                securityToken = dao.getSecurityToken(wfid);
                if (securityToken == null)
                    throw new UsernameNotFoundException("no such user");
                cache.put(wfid, securityToken);
            }
        } catch (NullPointerException npe) {
            throw new UsernameNotFoundException("no such user");
        }
        return new User(username, securityToken, true, true, true, true,
                Arrays.asList(new LiteralGrantedAuthority(SELF), new WorkflowSelfAuthority(wfid)));
    }

    @Override
    @PerfLogged
    protected final void additionalAuthenticationChecks(UserDetails userRecord,
            UsernamePasswordAuthenticationToken token) {
        try {
            additionalAuthenticationChecks(userRecord, token.getPrincipal(), token.getCredentials());
        } catch (AuthenticationException e) {
            throw e;
        } catch (Exception e) {
            log.warn("unexpected failure in authentication", e);
            throw new AuthenticationServiceException("unexpected failure in authentication", e);
        }
    }

    @Override
    @Nonnull
    @PerfLogged
    protected final UserDetails retrieveUser(String username, UsernamePasswordAuthenticationToken token) {
        try {
            return retrieveUser(username, token.getDetails());
        } catch (AuthenticationException e) {
            throw e;
        } catch (Exception e) {
            log.warn("unexpected failure in authentication", e);
            throw new AuthenticationServiceException("unexpected failure in authentication", e);
        }
    }

    @SuppressWarnings("serial")
    public static class WorkflowSelfAuthority extends LiteralGrantedAuthority {
        public WorkflowSelfAuthority(String wfid) {
            super(wfid);
        }

        public String getWorkflowID() {
            return getAuthority();
        }

        @Override
        public String toString() {
            return "WORKFLOW(" + getAuthority() + ")";
        }
    }

    public static class WorkflowSelfIDMapper implements LocalIdentityMapper {
        private Log log = LogFactory.getLog("Taverna.Server.UserDB");
        private RunStore runStore;

        @PreDestroy
        void closeLog() {
            log = null;
        }

        @Required
        public void setRunStore(RunStore runStore) {
            this.runStore = runStore;
        }

        private String getUsernameForSelfAccess(WorkflowSelfAuthority authority) throws UnknownRunException {
            return runStore.getRun(authority.getWorkflowID()).getSecurityContext().getOwner().getName();
        }

        @Override
        @PerfLogged
        public String getUsernameForPrincipal(UsernamePrincipal user) {
            Authentication auth = SecurityContextHolder.getContext().getAuthentication();
            if (auth == null || !auth.isAuthenticated())
                return null;
            try {
                for (GrantedAuthority authority : auth.getAuthorities())
                    if (authority instanceof WorkflowSelfAuthority)
                        return getUsernameForSelfAccess((WorkflowSelfAuthority) authority);
            } catch (UnknownRunException e) {
                log.warn("workflow run disappeared during computation of workflow map identity");
            }
            return null;
        }
    }
}