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    }