001 /**
002 * GRANITE DATA SERVICES
003 * Copyright (C) 2006-2013 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 */
022 package org.granite.seam21.security;
023
024 import java.lang.reflect.Constructor;
025 import java.lang.reflect.InvocationTargetException;
026 import java.lang.reflect.Method;
027 import java.util.ArrayList;
028 import java.util.Collections;
029 import java.util.List;
030 import java.util.Map;
031
032 import javax.security.auth.login.LoginException;
033
034 import org.granite.logging.Logger;
035 import org.granite.messaging.service.security.AbstractSecurityContext;
036 import org.granite.messaging.service.security.AbstractSecurityService;
037 import org.granite.messaging.service.security.SecurityServiceException;
038 import org.jboss.seam.contexts.Contexts;
039 import org.jboss.seam.core.Events;
040 import org.jboss.seam.international.StatusMessage;
041 import org.jboss.seam.international.StatusMessages;
042 import org.jboss.seam.security.AuthorizationException;
043 import org.jboss.seam.security.Identity;
044 import org.jboss.seam.security.NotLoggedInException;
045
046 /**
047 * @author Venkat DANDA
048 */
049 public class Seam21SecurityService extends AbstractSecurityService {
050
051 private static final Logger log = Logger.getLogger(Seam21SecurityService.class);
052
053 public void configure(Map<String, String> params) {
054 }
055
056 @SuppressWarnings("deprecation")
057 public void login(Object credentials, String charset) throws SecurityServiceException {
058 String[] decoded = decodeBase64Credentials(credentials, charset);
059
060 Contexts.getSessionContext().set("org.granite.seam.login", Boolean.TRUE);
061
062 Identity identity = Identity.instance();
063
064 // Unauthenticate if username has changed (otherwise the previous session/principal is reused)
065 if (identity.isLoggedIn(false) && !decoded[0].equals(identity.getUsername())) {
066 try {
067 Method method = identity.getClass().getDeclaredMethod("unAuthenticate");
068 method.setAccessible(true);
069 method.invoke(identity);
070 } catch (Exception e) {
071 log.error(e, "Could not call unAuthenticate method on: %s", identity.getClass());
072 }
073 }
074
075 identity.setUsername(decoded[0]);
076 identity.setPassword(decoded[1]);
077 try {
078 identity.authenticate();
079
080 endLogin(credentials, charset);
081 }
082 catch (LoginException e) {
083 // Force add of login error messages
084 try {
085 Method method = identity.getClass().getMethod("getCredentials");
086 Object cred = method.invoke(identity);
087 method = cred.getClass().getMethod("invalidate");
088 method.invoke(cred);
089 } catch (Exception f) {
090 log.error(f, "Could not call getCredentials().invalidate() method on: %s", identity.getClass());
091 }
092
093 if (Events.exists())
094 Events.instance().raiseEvent("org.jboss.seam.security.loginFailed", e);
095
096 throw SecurityServiceException.newInvalidCredentialsException("User authentication failed");
097 }
098 }
099
100 public Object authorize(AbstractSecurityContext context) throws Exception {
101 startAuthorization(context);
102
103 if (context.getDestination().isSecured()) {
104
105 Identity identity = Identity.instance();
106 if (!identity.isLoggedIn()) {
107 // TODO: Session expiration detection...
108 // throw SecurityServiceException.newSessionExpiredException("Session expired");
109 throw SecurityServiceException.newNotLoggedInException("User not logged in");
110 }
111
112 boolean accessDenied = true;
113 for (String role : context.getDestination().getRoles()) {
114 if (identity.hasRole(role)) {
115 accessDenied = false;
116 break;
117 }
118 }
119 if (accessDenied)
120 throw SecurityServiceException.newAccessDeniedException("User not in required role");
121 }
122
123 try {
124 return endAuthorization(context);
125 }
126 catch (InvocationTargetException e) {
127 for (Throwable t = e; t != null; t = t.getCause()) {
128 // If destination is not secured...
129 if (t instanceof NotLoggedInException)
130 throw SecurityServiceException.newNotLoggedInException("User not logged in");
131 // Don't create a dependency to javax.ejb in SecurityService...
132 if (t instanceof SecurityException ||
133 t instanceof AuthorizationException ||
134 "javax.ejb.EJBAccessException".equals(t.getClass().getName()))
135 throw SecurityServiceException.newAccessDeniedException(t.getMessage());
136 }
137 throw e;
138 }
139 }
140
141 public void logout() throws SecurityServiceException {
142 // if (Identity.instance().isLoggedIn(false)) ?
143 Identity.instance().logout();
144 }
145
146
147 @Override
148 @SuppressWarnings("unchecked")
149 public void handleSecurityException(SecurityServiceException e) {
150 List<StatusMessage> messages = Collections.emptyList();
151
152 try {
153 StatusMessages statusMessages = StatusMessages.instance();
154 if (statusMessages != null) {
155 // Execute and get the messages (once again reflection hack to use protected methods)
156 Method m = StatusMessages.class.getDeclaredMethod("doRunTasks");
157 m.setAccessible(true);
158 m.invoke(statusMessages);
159
160 Method m2 = StatusMessages.class.getDeclaredMethod("getMessages");
161 m2.setAccessible(true);
162 messages = (List<StatusMessage>)m2.invoke(statusMessages);
163 }
164 }
165 catch (Exception se) {
166 log.error("Could not get status messages", se);
167 }
168
169 List<Object> tideMessages = new ArrayList<Object>(messages.size());
170
171 try {
172 Class<?> c = Thread.currentThread().getContextClassLoader().loadClass("org.granite.tide.TideMessage");
173 Constructor<?> co = c.getConstructor(String.class, String.class, String.class);
174
175 for (StatusMessage fm : messages) {
176 String severity = null;
177 if (fm.getSeverity() == StatusMessage.Severity.INFO)
178 severity = "INFO";
179 else if (fm.getSeverity() == StatusMessage.Severity.WARN)
180 severity = "WARNING";
181 else if (fm.getSeverity() == StatusMessage.Severity.ERROR)
182 severity = "ERROR";
183 else if (fm.getSeverity() == StatusMessage.Severity.FATAL)
184 severity = "FATAL";
185
186 tideMessages.add(co.newInstance(severity, fm.getSummary(), fm.getDetail()));
187 }
188
189 e.getExtendedData().put("messages", tideMessages);
190 }
191 catch (Throwable t) {
192 e.getExtendedData().put("messages", tideMessages);
193 }
194 }
195 }