001    /**
002     * Licensed to the Apache Software Foundation (ASF) under one or more
003     * contributor license agreements.  See the NOTICE file distributed with
004     * this work for additional information regarding copyright ownership.
005     * The ASF licenses this file to You under the Apache License, Version 2.0
006     * (the "License"); you may not use this file except in compliance with
007     * the License.  You may obtain a copy of the License at
008     *
009     *      http://www.apache.org/licenses/LICENSE-2.0
010     *
011     * Unless required by applicable law or agreed to in writing, software
012     * distributed under the License is distributed on an "AS IS" BASIS,
013     * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
014     * See the License for the specific language governing permissions and
015     * limitations under the License.
016     */
017    package org.apache.camel.component.shiro.security;
018    
019    import java.io.ByteArrayInputStream;
020    import java.io.ObjectInputStream;
021    import java.util.ArrayList;
022    import java.util.List;
023    
024    import org.apache.camel.AsyncCallback;
025    import org.apache.camel.AsyncProcessor;
026    import org.apache.camel.CamelAuthorizationException;
027    import org.apache.camel.Exchange;
028    import org.apache.camel.Processor;
029    import org.apache.camel.impl.converter.AsyncProcessorTypeConverter;
030    import org.apache.camel.model.ProcessorDefinition;
031    import org.apache.camel.spi.AuthorizationPolicy;
032    import org.apache.camel.spi.RouteContext;
033    import org.apache.camel.util.AsyncProcessorHelper;
034    import org.apache.commons.logging.Log;
035    import org.apache.commons.logging.LogFactory;
036    import org.apache.shiro.SecurityUtils;
037    import org.apache.shiro.authc.AuthenticationException;
038    import org.apache.shiro.authc.IncorrectCredentialsException;
039    import org.apache.shiro.authc.LockedAccountException;
040    import org.apache.shiro.authc.UnknownAccountException;
041    import org.apache.shiro.authc.UsernamePasswordToken;
042    import org.apache.shiro.authz.Permission;
043    import org.apache.shiro.config.Ini;
044    import org.apache.shiro.config.IniSecurityManagerFactory;
045    import org.apache.shiro.crypto.AesCipherService;
046    import org.apache.shiro.crypto.CipherService;
047    import org.apache.shiro.mgt.SecurityManager;
048    import org.apache.shiro.subject.Subject;
049    import org.apache.shiro.util.ByteSource;
050    import org.apache.shiro.util.Factory;
051    
052    public class ShiroSecurityPolicy implements AuthorizationPolicy {
053        private static final transient Log LOG = LogFactory.getLog(ShiroSecurityPolicy.class);  
054        private final byte[] bits128 = {
055            (byte) 0x08, (byte) 0x09, (byte) 0x0A, (byte) 0x0B,
056            (byte) 0x0C, (byte) 0x0D, (byte) 0x0E, (byte) 0x0F,
057            (byte) 0x10, (byte) 0x11, (byte) 0x12, (byte) 0x13,
058            (byte) 0x14, (byte) 0x15, (byte) 0x16, (byte) 0x17};
059        private CipherService cipherService;
060        private byte[] passPhrase;
061        private SecurityManager securityManager;
062        private List<Permission> permissionsList;
063        private boolean alwaysReauthenticate;
064        
065        public ShiroSecurityPolicy() {
066            this.passPhrase = bits128;
067            // Set up AES encryption based cipher service, by default 
068            cipherService = new AesCipherService();
069            permissionsList = new ArrayList<Permission>();
070            alwaysReauthenticate = true;
071        }   
072        
073        public ShiroSecurityPolicy(String iniResourcePath) {
074            this();
075            Factory<SecurityManager> factory = new IniSecurityManagerFactory(iniResourcePath);
076            securityManager = (SecurityManager) factory.getInstance();
077            SecurityUtils.setSecurityManager(securityManager);
078        }
079        
080        public ShiroSecurityPolicy(Ini ini) {
081            this();
082            Factory<SecurityManager> factory = new IniSecurityManagerFactory(ini);
083            securityManager = (SecurityManager) factory.getInstance();
084            SecurityUtils.setSecurityManager(securityManager);
085        }
086        
087        public ShiroSecurityPolicy(String iniResourcePath, byte[] passPhrase) {
088            this(iniResourcePath);        
089            this.setPassPhrase(passPhrase);
090        }
091    
092        public ShiroSecurityPolicy(Ini ini, byte[] passPhrase) {
093            this(ini);        
094            this.setPassPhrase(passPhrase);
095        }
096        
097        public ShiroSecurityPolicy(String iniResourcePath, byte[] passPhrase, boolean alwaysReauthenticate) {
098            this(iniResourcePath, passPhrase); 
099            this.setAlwaysReauthenticate(alwaysReauthenticate);
100        }
101    
102        public ShiroSecurityPolicy(Ini ini, byte[] passPhrase, boolean alwaysReauthenticate) {
103            this(ini, passPhrase); 
104            this.setAlwaysReauthenticate(alwaysReauthenticate);
105        }
106        
107        public ShiroSecurityPolicy(String iniResourcePath, byte[] passPhrase, boolean alwaysReauthenticate, List<Permission> permissionsList) {
108            this(iniResourcePath, passPhrase, alwaysReauthenticate); 
109            this.setPermissionsList(permissionsList);
110        }
111        
112        public ShiroSecurityPolicy(Ini ini, byte[] passPhrase, boolean alwaysReauthenticate, List<Permission> permissionsList) {
113            this(ini, passPhrase, alwaysReauthenticate); 
114            this.setPermissionsList(permissionsList);
115        }
116    
117        public void beforeWrap(RouteContext routeContext, ProcessorDefinition<?> definition) {  
118            //Not implemented
119        }
120        
121        public Processor wrap(RouteContext routeContext, final Processor processor) {        
122            return new AsyncProcessor() {
123                public boolean process(Exchange exchange, final AsyncCallback callback)  {
124                    boolean sync;
125                    try {
126                        applySecurityPolicy(exchange);
127                    } catch (Exception e) {
128                        // exception occurred so break out
129                        exchange.setException(e);
130                        callback.done(true);
131                        return true;
132                    }
133                    
134                    // If here, then user is authenticated and authorized
135                    // Now let the original processor continue routing supporting the async routing engine
136                    AsyncProcessor ap = AsyncProcessorTypeConverter.convert(processor);
137                    sync = AsyncProcessorHelper.process(ap, exchange, new AsyncCallback() {
138                        public void done(boolean doneSync) {
139                            // we only have to handle async completion of this policy
140                            if (doneSync) {
141                                return;
142                            }
143                            callback.done(false);
144                        }
145                    });                    
146                    
147                    if (!sync) {
148                        // if async, continue routing async
149                        return false;
150                    }
151    
152                    // we are done synchronously, so do our after work and invoke the callback
153                    callback.done(true);
154                    return true;                
155                }
156    
157                public void process(Exchange exchange) throws Exception {
158                    applySecurityPolicy(exchange);               
159                    processor.process(exchange);
160                }
161                
162                private void applySecurityPolicy(Exchange exchange) throws Exception {
163                    ByteSource encryptedToken = (ByteSource)exchange.getIn().getHeader("SHIRO_SECURITY_TOKEN");
164                    ByteSource decryptedToken = getCipherService().decrypt(encryptedToken.getBytes(), getPassPhrase());
165                    
166                    ByteArrayInputStream byteArrayInputStream = new ByteArrayInputStream(decryptedToken.getBytes());
167                    ObjectInputStream objectInputStream = new ObjectInputStream(byteArrayInputStream);
168                    ShiroSecurityToken securityToken = (ShiroSecurityToken)objectInputStream.readObject();
169                    objectInputStream.close();
170                    byteArrayInputStream.close();
171                    
172                    Subject currentUser = SecurityUtils.getSubject();
173                    
174                    // Authenticate user if not authenticated
175                    try {
176                        authenticateUser(currentUser, securityToken);
177                    
178                        // Test whether user's role is authorized to perform functions in the permissions list  
179                        authorizeUser(currentUser, exchange);
180                    } finally {
181                        if (alwaysReauthenticate) {
182                            currentUser.logout();
183                            currentUser = null;
184                        }
185                    }
186    
187                }
188            };
189        }
190    
191        private void authenticateUser(Subject currentUser, ShiroSecurityToken securityToken) {
192            if (!currentUser.isAuthenticated()) {
193                UsernamePasswordToken token = new UsernamePasswordToken(securityToken.getUsername(), securityToken.getPassword());
194                if (alwaysReauthenticate) {
195                    token.setRememberMe(false);
196                } else {
197                    token.setRememberMe(true);
198                }
199                
200                try {
201                    currentUser.login(token);
202                    if (LOG.isDebugEnabled()) {
203                        LOG.debug("Current User " + currentUser.getPrincipal() + " successfully authenticated");
204                    }
205                } catch (UnknownAccountException uae) {
206                    throw new UnknownAccountException("Authentication Failed. There is no user with username of " + token.getPrincipal(), uae.getCause());
207                } catch (IncorrectCredentialsException ice) {
208                    throw new IncorrectCredentialsException("Authentication Failed. Password for account " + token.getPrincipal() + " was incorrect!", ice.getCause());
209                } catch (LockedAccountException lae) {
210                    throw new LockedAccountException("Authentication Failed. The account for username " + token.getPrincipal() + " is locked."
211                        + "Please contact your administrator to unlock it.", lae.getCause());
212                } catch (AuthenticationException ae) {
213                    throw new AuthenticationException("Authentication Failed.", ae.getCause());
214                }
215            }
216        }
217        
218        private void authorizeUser(Subject currentUser, Exchange exchange) throws CamelAuthorizationException {
219            boolean authorized = false;
220            if (!permissionsList.isEmpty()) {
221                for (Permission permission : permissionsList) {
222                    if (currentUser.isPermitted(permission)) {
223                        authorized = true;
224                        break;
225                    }
226                }
227            } else {
228                if (LOG.isDebugEnabled()) {
229                    LOG.debug("Valid Permissions List not specified for ShiroSecurityPolicy. No authorization checks will be performed for current user");
230                }
231                authorized = true;
232            }
233            
234            if (!authorized) {
235                throw new CamelAuthorizationException("Authorization Failed. Subject's role set does not have the necessary permissions to perform further processing", exchange);
236            } 
237            
238            if (LOG.isDebugEnabled()) {
239                LOG.debug("Current User " + currentUser.getPrincipal() + " is successfully authorized. The exchange will be allowed to proceed");
240            }
241        }
242        
243        public CipherService getCipherService() {
244            return cipherService;
245        }
246    
247        public void setCipherService(CipherService cipherService) {
248            this.cipherService = cipherService;
249        }
250    
251        public SecurityManager getSecurityManager() {
252            return securityManager;
253        }
254    
255        public void setSecurityManager(SecurityManager securityManager) {
256            this.securityManager = securityManager;
257        }
258    
259        public byte[] getPassPhrase() {
260            return passPhrase;
261        }
262    
263        public void setPassPhrase(byte[] passPhrase) {
264            this.passPhrase = passPhrase;
265        }
266    
267        public List<Permission> getPermissionsList() {
268            return permissionsList;
269        }
270    
271        public void setPermissionsList(List<Permission> permissionsList) {
272            this.permissionsList = permissionsList;
273        }
274    
275        public boolean isAlwaysReauthenticate() {
276            return alwaysReauthenticate;
277        }
278    
279        public void setAlwaysReauthenticate(boolean alwaysReauthenticate) {
280            this.alwaysReauthenticate = alwaysReauthenticate;
281        }
282     
283    }