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    }