001/** 002 * GRANITE DATA SERVICES 003 * Copyright (C) 2006-2014 GRANITE DATA SERVICES S.A.S. 004 * 005 * This file is part of the Granite Data Services Platform. 006 * 007 * Granite Data Services is free software; you can redistribute it and/or 008 * modify it under the terms of the GNU Lesser General Public 009 * License as published by the Free Software Foundation; either 010 * version 2.1 of the License, or (at your option) any later version. 011 * 012 * Granite Data Services is distributed in the hope that it will be useful, 013 * but WITHOUT ANY WARRANTY; without even the implied warranty of 014 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser 015 * General Public License for more details. 016 * 017 * You should have received a copy of the GNU Lesser General Public 018 * License along with this library; if not, write to the Free Software 019 * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, 020 * USA, or see <http://www.gnu.org/licenses/>. 021 */ 022package org.granite.seam21.security; 023 024import java.lang.reflect.Constructor; 025import java.lang.reflect.InvocationTargetException; 026import java.lang.reflect.Method; 027import java.security.Principal; 028import java.util.ArrayList; 029import java.util.Collections; 030import java.util.List; 031import java.util.Map; 032 033import javax.security.auth.login.LoginException; 034 035import org.granite.logging.Logger; 036import org.granite.messaging.service.security.AbstractSecurityContext; 037import org.granite.messaging.service.security.AbstractSecurityService; 038import org.granite.messaging.service.security.SecurityServiceException; 039import org.jboss.seam.contexts.Contexts; 040import org.jboss.seam.core.Events; 041import org.jboss.seam.international.StatusMessage; 042import org.jboss.seam.international.StatusMessages; 043import org.jboss.seam.security.AuthorizationException; 044import org.jboss.seam.security.Identity; 045import org.jboss.seam.security.NotLoggedInException; 046 047/** 048 * @author Venkat DANDA 049 */ 050public class Seam21SecurityService extends AbstractSecurityService { 051 052 private static final Logger log = Logger.getLogger(Seam21SecurityService.class); 053 054 public void configure(Map<String, String> params) { 055 } 056 057 @SuppressWarnings("deprecation") 058 public Principal login(Object credentials, String charset) throws SecurityServiceException { 059 String[] decoded = decodeBase64Credentials(credentials, charset); 060 061 Contexts.getSessionContext().set("org.granite.seam.login", Boolean.TRUE); 062 063 Identity identity = Identity.instance(); 064 065 // Unauthenticate if username has changed (otherwise the previous session/principal is reused) 066 if (identity.isLoggedIn(false) && !decoded[0].equals(identity.getUsername())) { 067 try { 068 Method method = identity.getClass().getDeclaredMethod("unAuthenticate"); 069 method.setAccessible(true); 070 method.invoke(identity); 071 } catch (Exception e) { 072 log.error(e, "Could not call unAuthenticate method on: %s", identity.getClass()); 073 } 074 } 075 076 identity.setUsername(decoded[0]); 077 identity.setPassword(decoded[1]); 078 try { 079 identity.authenticate(); 080 081 endLogin(credentials, charset); 082 } 083 catch (LoginException e) { 084 // Force add of login error messages 085 try { 086 Method method = identity.getClass().getMethod("getCredentials"); 087 Object cred = method.invoke(identity); 088 method = cred.getClass().getMethod("invalidate"); 089 method.invoke(cred); 090 } catch (Exception f) { 091 log.error(f, "Could not call getCredentials().invalidate() method on: %s", identity.getClass()); 092 } 093 094 if (Events.exists()) 095 Events.instance().raiseEvent("org.jboss.seam.security.loginFailed", e); 096 097 throw SecurityServiceException.newInvalidCredentialsException("User authentication failed"); 098 } 099 100 return identity.getPrincipal(); 101 } 102 103 public Object authorize(AbstractSecurityContext context) throws Exception { 104 startAuthorization(context); 105 106 if (context.getDestination().isSecured()) { 107 108 Identity identity = Identity.instance(); 109 if (!identity.isLoggedIn()) { 110 // TODO: Session expiration detection... 111 // throw SecurityServiceException.newSessionExpiredException("Session expired"); 112 throw SecurityServiceException.newNotLoggedInException("User not logged in"); 113 } 114 115 boolean accessDenied = true; 116 for (String role : context.getDestination().getRoles()) { 117 if (identity.hasRole(role)) { 118 accessDenied = false; 119 break; 120 } 121 } 122 if (accessDenied) 123 throw SecurityServiceException.newAccessDeniedException("User not in required role"); 124 } 125 126 try { 127 return endAuthorization(context); 128 } 129 catch (InvocationTargetException e) { 130 for (Throwable t = e; t != null; t = t.getCause()) { 131 // If destination is not secured... 132 if (t instanceof NotLoggedInException) 133 throw SecurityServiceException.newNotLoggedInException("User not logged in"); 134 // Don't create a dependency to javax.ejb in SecurityService... 135 if (t instanceof SecurityException || 136 t instanceof AuthorizationException || 137 "javax.ejb.EJBAccessException".equals(t.getClass().getName())) 138 throw SecurityServiceException.newAccessDeniedException(t.getMessage()); 139 } 140 throw e; 141 } 142 } 143 144 public void logout() throws SecurityServiceException { 145 // if (Identity.instance().isLoggedIn(false)) ? 146 Identity.instance().logout(); 147 } 148 149 150 @Override 151 @SuppressWarnings("unchecked") 152 public void handleSecurityException(SecurityServiceException e) { 153 List<StatusMessage> messages = Collections.emptyList(); 154 155 try { 156 StatusMessages statusMessages = StatusMessages.instance(); 157 if (statusMessages != null) { 158 // Execute and get the messages (once again reflection hack to use protected methods) 159 Method m = StatusMessages.class.getDeclaredMethod("doRunTasks"); 160 m.setAccessible(true); 161 m.invoke(statusMessages); 162 163 Method m2 = StatusMessages.class.getDeclaredMethod("getMessages"); 164 m2.setAccessible(true); 165 messages = (List<StatusMessage>)m2.invoke(statusMessages); 166 } 167 } 168 catch (Exception se) { 169 log.error("Could not get status messages", se); 170 } 171 172 List<Object> tideMessages = new ArrayList<Object>(messages.size()); 173 174 try { 175 Class<?> c = Thread.currentThread().getContextClassLoader().loadClass("org.granite.tide.TideMessage"); 176 Constructor<?> co = c.getConstructor(String.class, String.class, String.class); 177 178 for (StatusMessage fm : messages) { 179 String severity = null; 180 if (fm.getSeverity() == StatusMessage.Severity.INFO) 181 severity = "INFO"; 182 else if (fm.getSeverity() == StatusMessage.Severity.WARN) 183 severity = "WARNING"; 184 else if (fm.getSeverity() == StatusMessage.Severity.ERROR) 185 severity = "ERROR"; 186 else if (fm.getSeverity() == StatusMessage.Severity.FATAL) 187 severity = "FATAL"; 188 189 tideMessages.add(co.newInstance(severity, fm.getSummary(), fm.getDetail())); 190 } 191 192 e.getExtendedData().put("messages", tideMessages); 193 } 194 catch (Throwable t) { 195 e.getExtendedData().put("messages", tideMessages); 196 } 197 } 198}