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    }