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.seam.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.List;
028 import java.util.Map;
029
030 import javax.faces.application.FacesMessage;
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.faces.FacesMessages;
039 import org.jboss.seam.security.AuthorizationException;
040 import org.jboss.seam.security.Identity;
041 import org.jboss.seam.security.NotLoggedInException;
042
043 /**
044 * @author Venkat DANDA
045 */
046 public class SeamSecurityService extends AbstractSecurityService {
047
048 private static final Logger log = Logger.getLogger(SeamSecurityService.class);
049
050 public void configure(Map<String, String> params) {
051 }
052
053 public void login(Object credentials, String charset) throws SecurityServiceException {
054 String[] decoded = decodeBase64Credentials(credentials, charset);
055
056 Contexts.getSessionContext().set("org.granite.seam.login", Boolean.TRUE);
057
058 Identity identity = Identity.instance();
059
060 // Unauthenticate if username has changed (otherwise the previous session/principal is reused)
061 if (identity.isLoggedIn(false) && !decoded[0].equals(identity.getUsername())) {
062 try {
063 Method method = identity.getClass().getDeclaredMethod("unAuthenticate");
064 method.setAccessible(true);
065 method.invoke(identity);
066 } catch (Exception e) {
067 log.error(e, "Could not call unAuthenticate method on: %s", identity.getClass());
068 }
069 }
070
071 identity.setUsername(decoded[0]);
072 identity.setPassword(decoded[1]);
073 try {
074 identity.authenticate();
075
076 endLogin(credentials, charset);
077 }
078 catch (LoginException e) {
079 identity.login(); // Force add of login error messages
080
081 handleAuthenticationExceptions(e);
082 }
083 }
084
085 protected void handleAuthenticationExceptions(LoginException e) {
086 throw SecurityServiceException.newInvalidCredentialsException("User authentication failed", e.getMessage());
087 }
088
089 public Object authorize(AbstractSecurityContext context) throws Exception {
090 startAuthorization(context);
091
092 if (context.getDestination().isSecured()) {
093
094 Identity identity = Identity.instance();
095 if (!identity.isLoggedIn()) {
096 // TODO: Session expiration detection...
097 // throw SecurityServiceException.newSessionExpiredException("Session expired");
098 throw SecurityServiceException.newNotLoggedInException("User not logged in");
099 }
100
101 boolean accessDenied = true;
102 for (String role : context.getDestination().getRoles()) {
103 if (identity.hasRole(role)) {
104 accessDenied = false;
105 break;
106 }
107 }
108 if (accessDenied)
109 throw SecurityServiceException.newAccessDeniedException("User not in required role");
110 }
111
112 try {
113 return endAuthorization(context);
114 } catch (InvocationTargetException e) {
115 for (Throwable t = e; t != null; t = t.getCause()) {
116 // If destination is not secured...
117 if (t instanceof NotLoggedInException)
118 throw SecurityServiceException.newNotLoggedInException("User not logged in");
119 // Don't create a dependency to javax.ejb in SecurityService...
120 if (t instanceof SecurityException ||
121 t instanceof AuthorizationException ||
122 "javax.ejb.EJBAccessException".equals(t.getClass().getName()))
123 throw SecurityServiceException.newAccessDeniedException(t.getMessage());
124 }
125 throw e;
126 }
127 }
128
129 public void logout() throws SecurityServiceException {
130 // if (Identity.instance().isLoggedIn(false)) ?
131 Identity.instance().logout();
132 }
133
134
135 @Override
136 public void handleSecurityException(SecurityServiceException e) {
137 // Add messages
138 //Prepare for the messages. First step is convert the tasks to Seam FacesMessages
139 FacesMessages.afterPhase();
140 //Second step is add the Seam FacesMessages to JSF FacesContext Messages
141 FacesMessages.instance().beforeRenderResponse();
142
143 List<FacesMessage> facesMessages = FacesMessages.instance().getCurrentMessages();
144
145 try {
146 Class<?> c = Thread.currentThread().getContextClassLoader().loadClass("org.granite.tide.TideMessage");
147 Constructor<?> co = c.getConstructor(String.class, String.class, String.class);
148
149 List<Object> tideMessages = new ArrayList<Object>(facesMessages.size());
150 for (FacesMessage fm : facesMessages) {
151 String severity = null;
152 if (fm.getSeverity() == FacesMessage.SEVERITY_INFO)
153 severity = "INFO";
154 else if (fm.getSeverity() == FacesMessage.SEVERITY_WARN)
155 severity = "WARNING";
156 else if (fm.getSeverity() == FacesMessage.SEVERITY_ERROR)
157 severity = "ERROR";
158 else if (fm.getSeverity() == FacesMessage.SEVERITY_FATAL)
159 severity = "FATAL";
160
161 tideMessages.add(co.newInstance(severity, fm.getSummary(), fm.getDetail()));
162 }
163
164 e.getExtendedData().put("messages", tideMessages);
165 }
166 catch (Throwable t) {
167 e.getExtendedData().put("messages", facesMessages);
168 }
169 }
170 }