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