/**
* GRANITE DATA SERVICES
* Copyright (C) 2006-2015 GRANITE DATA SERVICES S.A.S.
*
* This file is part of the Granite Data Services Platform.
*
* Granite Data Services is free software; you can redistribute it and/or
* modify it under the terms of the GNU Lesser General Public
* License as published by the Free Software Foundation; either
* version 2.1 of the License, or (at your option) any later version.
*
* Granite Data Services is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU Lesser
* General Public License for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the Free Software
* Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301,
* USA, or see <http://www.gnu.org/licenses/>.
*/
package org.granite.messaging.service.security;
import io.undertow.security.api.SecurityContext;
import io.undertow.server.HttpServerExchange;
import io.undertow.servlet.spec.HttpServletRequestImpl;
import org.granite.context.GraniteContext;
import org.granite.messaging.service.security.SecurityServiceException;
import org.granite.messaging.webapp.HttpGraniteContext;
import org.granite.messaging.webapp.ServletGraniteContext;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpSession;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.security.Principal;
import java.util.Map;
/**
* Created by william on 03/01/14.
*/
public class UndertowSecurityService extends AbstractSecurityService {
public void configure(Map<String, String> params) {
}
@Override
public void prelogin(HttpSession session, Object request, String servletName) {
if (session == null) // Cannot prelogin() without a session
return;
if (session.getAttribute(AuthenticationContext.class.getName()) instanceof UndertowAuthenticationContext)
return;
HttpServerExchange exchange = null;
if (request instanceof HttpServletRequestImpl)
exchange = ((HttpServletRequestImpl)request).getExchange();
else if (request.getClass().getSimpleName().equals("ExchangeHandshakeRequest")) { // Websocket
Field f = null;
try {
f = request.getClass().getDeclaredField("exchange");
f.setAccessible(true);
Object wsExchange = f.get(request);
f = wsExchange.getClass().getDeclaredField("exchange");
f.setAccessible(true);
exchange = (HttpServerExchange)f.get(wsExchange);
}
catch (Exception e) {
throw new RuntimeException("Could not get internal undertow exchange", e);
}
}
SecurityContext securityContext = exchange.getSecurityContext();
UndertowAuthenticationContext authenticationContext = new UndertowAuthenticationContext(securityContext);
session.setAttribute(AuthenticationContext.class.getName(), authenticationContext);
}
public Principal login(Object credentials, String charset) throws SecurityServiceException {
String[] decoded = decodeBase64Credentials(credentials, charset);
ServletGraniteContext graniteContext = (ServletGraniteContext)GraniteContext.getCurrentInstance();
Principal principal = null;
if (graniteContext instanceof HttpGraniteContext) {
HttpServletRequest httpRequest = graniteContext.getRequest();
HttpServerExchange exchange = ((HttpServletRequestImpl)httpRequest).getExchange();
SecurityContext securityContext = exchange.getSecurityContext();
UndertowAuthenticationContext authenticationContext = new UndertowAuthenticationContext(securityContext);
principal = authenticationContext.authenticate(decoded[0], decoded[1]);
if (principal != null)
graniteContext.getSession().setAttribute(AuthenticationContext.class.getName(), authenticationContext);
}
else {
AuthenticationContext authenticationContext = (AuthenticationContext)graniteContext.getSession().getAttribute(AuthenticationContext.class.getName());
if (authenticationContext != null)
principal = authenticationContext.authenticate(decoded[0], decoded[1]);
else
return null;
}
if (principal == null)
throw SecurityServiceException.newInvalidCredentialsException("Wrong username or password");
graniteContext.setPrincipal(principal);
endLogin(credentials, charset);
return principal;
}
public Object authorize(AbstractSecurityContext context) throws Exception {
startAuthorization(context);
ServletGraniteContext graniteContext = (ServletGraniteContext)GraniteContext.getCurrentInstance();
HttpServletRequest httpRequest = null;
AuthenticationContext authenticationContext = null;
Principal principal = null;
if (graniteContext instanceof HttpGraniteContext) {
httpRequest = graniteContext.getRequest();
HttpServerExchange exchange = ((HttpServletRequestImpl)httpRequest).getExchange();
if (exchange.getSecurityContext() == null || exchange.getSecurityContext().getAuthenticatedAccount() == null)
tryRelogin();
if (exchange.getSecurityContext() != null && exchange.getSecurityContext().getAuthenticatedAccount() != null)
principal = exchange.getSecurityContext().getAuthenticatedAccount().getPrincipal();
}
else {
HttpSession session = graniteContext.getSession(false);
if (session != null) {
authenticationContext = (AuthenticationContext)session.getAttribute(AuthenticationContext.class.getName());
if (authenticationContext != null)
principal = authenticationContext.getPrincipal();
}
}
graniteContext.setPrincipal(principal);
if (context.getDestination().isSecured()) {
if (principal == null) {
if (httpRequest != null && httpRequest.getRequestedSessionId() != null) {
HttpSession httpSession = httpRequest.getSession(false);
if (httpSession == null || !httpRequest.getRequestedSessionId().equals(httpSession.getId()))
throw SecurityServiceException.newSessionExpiredException("Session expired");
}
throw SecurityServiceException.newNotLoggedInException("User not logged in");
}
if (httpRequest == null && authenticationContext == null)
throw SecurityServiceException.newNotLoggedInException("No authorization context");
boolean accessDenied = true;
for (String role : context.getDestination().getRoles()) {
if (httpRequest != null && httpRequest.isUserInRole(role)) {
accessDenied = false;
break;
}
if (authenticationContext != null && authenticationContext.isUserInRole(role)) {
accessDenied = false;
break;
}
}
if (accessDenied)
throw SecurityServiceException.newAccessDeniedException("User not in required role");
}
try {
return endAuthorization(context);
}
catch (InvocationTargetException e) {
for (Throwable t = e; t != null; t = t.getCause()) {
// Don't create a dependency to javax.ejb in SecurityService...
if (t instanceof SecurityException ||
"javax.ejb.EJBAccessException".equals(t.getClass().getName()))
throw SecurityServiceException.newAccessDeniedException(t.getMessage());
}
throw e;
}
}
public void logout() throws SecurityServiceException {
ServletGraniteContext graniteContext = (ServletGraniteContext)GraniteContext.getCurrentInstance();
if (graniteContext instanceof HttpGraniteContext) {
HttpServletRequest httpRequest = graniteContext.getRequest();
HttpServerExchange exchange = ((HttpServletRequestImpl)httpRequest).getExchange();
SecurityContext securityContext = exchange.getSecurityContext();
securityContext.logout();
}
HttpSession session = graniteContext.getSession(false);
if (session != null) {
endLogout();
session.invalidate();
}
}
public static class UndertowAuthenticationContext implements AuthenticationContext {
private static final long serialVersionUID = 1L;
private transient final SecurityContext securityContext;
private transient Principal principal = null;
private String username = null;
public UndertowAuthenticationContext(SecurityContext securityContext) {
this.securityContext = securityContext;
}
public Principal authenticate(String username, String password) {
if (securityContext == null)
throw SecurityServiceException.newAuthenticationFailedException("Invalid authentication");
if (username.equals(this.username) && principal != null)
return principal;
boolean authenticated = securityContext.login(username, password);
if (authenticated) {
this.username = username;
this.principal = securityContext.getAuthenticatedAccount().getPrincipal();
}
return principal;
}
public Principal getPrincipal() {
return principal;
}
public boolean isUserInRole(String role) {
return securityContext.getAuthenticatedAccount().getRoles().contains(role);
}
public void logout() {
securityContext.logout();
}
}
}