001/*
002 * Licensed to the Apache Software Foundation (ASF) under one
003 * or more contributor license agreements.  See the NOTICE file
004 * distributed with this work for additional information
005 * regarding copyright ownership.  The ASF licenses this file
006 * to you under the Apache License, Version 2.0 (the
007 * "License"); you may not use this file except in compliance
008 * with the License.  You may obtain a copy of the License at
009 *
010 *     http://www.apache.org/licenses/LICENSE-2.0
011 *
012 * Unless required by applicable law or agreed to in writing,
013 * software distributed under the License is distributed on an
014 * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
015 * KIND, either express or implied.  See the License for the
016 * specific language governing permissions and limitations
017 * under the License.
018 */
019package org.apache.shiro.spring.remoting;
020
021import org.apache.shiro.SecurityUtils;
022import org.apache.shiro.mgt.SecurityManager;
023import org.apache.shiro.subject.ExecutionException;
024import org.apache.shiro.subject.Subject;
025import org.slf4j.Logger;
026import org.slf4j.LoggerFactory;
027import org.springframework.remoting.support.DefaultRemoteInvocationExecutor;
028import org.springframework.remoting.support.RemoteInvocation;
029
030import java.io.Serializable;
031import java.lang.reflect.InvocationTargetException;
032import java.util.concurrent.Callable;
033
034
035/**
036 * An implementation of the Spring {@link org.springframework.remoting.support.RemoteInvocationExecutor}
037 * that binds a {@code sessionId} to the incoming thread to make it available to the {@code SecurityManager}
038 * implementation during the thread execution.  The {@code SecurityManager} implementation can use this sessionId
039 * to reconstitute the {@code Subject} instance based on persistent state in the corresponding {@code Session}.
040 *
041 * @since 0.1
042 */
043public class SecureRemoteInvocationExecutor extends DefaultRemoteInvocationExecutor {
044
045    //TODO - complete JavaDoc
046
047    /*--------------------------------------------
048    |             C O N S T A N T S             |
049    ============================================*/
050
051    /*--------------------------------------------
052    |    I N S T A N C E   V A R I A B L E S    |
053    ============================================*/
054    private static final Logger log = LoggerFactory.getLogger(SecureRemoteInvocationExecutor.class);
055
056    /**
057     * The SecurityManager used to retrieve realms that should be associated with the
058     * created <tt>Subject</tt>s upon remote invocation.
059     */
060    private SecurityManager securityManager;
061
062    /*--------------------------------------------
063    |         C O N S T R U C T O R S           |
064    ============================================*/
065
066    /*--------------------------------------------
067    |  A C C E S S O R S / M O D I F I E R S    |
068    ============================================*/
069
070    public void setSecurityManager(org.apache.shiro.mgt.SecurityManager securityManager) {
071        this.securityManager = securityManager;
072    }
073
074    /*--------------------------------------------
075    |               M E T H O D S               |
076    ============================================*/
077    @SuppressWarnings({"unchecked"})
078    public Object invoke(final RemoteInvocation invocation, final Object targetObject)
079            throws NoSuchMethodException, IllegalAccessException, InvocationTargetException {
080
081        try {
082            SecurityManager securityManager =
083                    this.securityManager != null ? this.securityManager : SecurityUtils.getSecurityManager();
084
085            Subject.Builder builder = new Subject.Builder(securityManager);
086
087            String host = (String) invocation.getAttribute(SecureRemoteInvocationFactory.HOST_KEY);
088            if (host != null) {
089                builder.host(host);
090            }
091
092            Serializable sessionId = invocation.getAttribute(SecureRemoteInvocationFactory.SESSION_ID_KEY);
093            if (sessionId != null) {
094                builder.sessionId(sessionId);
095            } else {
096                if (log.isTraceEnabled()) {
097                    log.trace("RemoteInvocation did not contain a Shiro Session id attribute under " +
098                            "key [" + SecureRemoteInvocationFactory.SESSION_ID_KEY + "].  A Subject based " +
099                            "on an existing Session will not be available during the method invocatin.");
100                }
101            }
102
103            Subject subject = builder.buildSubject();
104            return subject.execute(new Callable() {
105                public Object call() throws Exception {
106                    return SecureRemoteInvocationExecutor.super.invoke(invocation, targetObject);
107                }
108            });
109        } catch (ExecutionException e) {
110            Throwable cause = e.getCause();
111            if (cause instanceof NoSuchMethodException) {
112                throw (NoSuchMethodException) cause;
113            } else if (cause instanceof IllegalAccessException) {
114                throw (IllegalAccessException) cause;
115            } else if (cause instanceof InvocationTargetException) {
116                throw (InvocationTargetException) cause;
117            } else {
118                throw new InvocationTargetException(cause);
119            }
120        } catch (Throwable t) {
121            throw new InvocationTargetException(t);
122        }
123    }
124}