package co.codewizards.cloudstore.ls.rest.server.auth; import co.codewizards.cloudstore.core.io.ByteArrayInputStream; import java.io.CharArrayReader; import java.io.CharArrayWriter; import java.io.IOException; import java.io.InputStreamReader; import java.io.Reader; import java.io.UnsupportedEncodingException; import java.security.Principal; import java.util.Arrays; import javax.servlet.http.HttpServletRequest; import javax.ws.rs.NotAuthorizedException; import javax.ws.rs.WebApplicationException; import javax.ws.rs.container.ContainerRequestContext; import javax.ws.rs.container.ContainerRequestFilter; import javax.ws.rs.core.Context; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; import javax.ws.rs.core.Response.Status; import javax.ws.rs.core.SecurityContext; import javax.ws.rs.core.UriInfo; import org.glassfish.jersey.internal.util.Base64; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import co.codewizards.cloudstore.core.dto.Error; import co.codewizards.cloudstore.core.util.IOUtil; public class AuthFilter implements ContainerRequestFilter { private static final Logger logger = LoggerFactory.getLogger(AuthFilter.class); protected @Context UriInfo uriInfo; protected @Context HttpServletRequest request; @Override public void filter(ContainerRequestContext requestContext) throws IOException { final String authorizationHeader = request.getHeader("Authorization"); if (authorizationHeader == null || authorizationHeader.isEmpty()) { logger.debug("getAuth: There is no 'Authorization' header. Replying with a Status.UNAUTHORIZED response asking for 'Basic' authentication."); throw newUnauthorizedException(); } logger.debug("getAuth: 'Authorization' header: {}", authorizationHeader); if (!authorizationHeader.startsWith("Basic")) throw new WebApplicationException(Response.status(Status.FORBIDDEN) .type(MediaType.APPLICATION_XML) .entity(new Error("Only 'Basic' authentication is supported!")).build()); final String basicAuthEncoded = authorizationHeader.substring("Basic".length()).trim(); final byte[] basicAuthDecodedBA = getBasicAuthEncodedBA(basicAuthEncoded); final StringBuilder userNameSB = new StringBuilder(); char[] password = null; final ByteArrayInputStream in = new ByteArrayInputStream(basicAuthDecodedBA); CharArrayWriter caw = new CharArrayWriter(basicAuthDecodedBA.length + 1); CharArrayReader car = null; try { final Reader r = new InputStreamReader(in, IOUtil.CHARSET_NAME_UTF_8); int charsReadTotal = 0; int charsRead; do { final char[] c = new char[10]; charsRead = r.read(c); caw.write(c); if (charsRead > 0) charsReadTotal += charsRead; } while (charsRead >= 0); charsRead = 0; car = new CharArrayReader(caw.toCharArray()); int charsReadTotalCheck = 0; while (charsRead >= 0 && charsRead < charsReadTotal) { final char[] cbuf = new char[1]; charsRead = car.read(cbuf); if (charsRead > 0) charsReadTotalCheck += charsRead; if (cbuf[0] == ':') break; userNameSB.append(cbuf[0]); } if (charsRead >= 0 && charsRead < charsReadTotal) { password = new char[charsReadTotal - charsReadTotalCheck]; final int passwordSize = car.read(password); if (passwordSize + charsReadTotalCheck != charsReadTotal) throw new IllegalStateException("passwordSize and charsRead must match charsReadTotal!" + " passwordSize=" + passwordSize + ", charsRead=" + charsRead + ", charsReadTotal=" + charsReadTotal);//TODO for testing } } catch (final Exception e) { throw new WebApplicationException(Response.status(Status.INTERNAL_SERVER_ERROR).type(MediaType.APPLICATION_XML).entity(new Error(e)).build()); } finally { // For extra safety: Overwrite all sensitive memory with 0. Arrays.fill(basicAuthDecodedBA, (byte)0); final char[] zeroArray = new char[] {0}; // overwrite caw & car: if (caw != null) { final int oldCawSize = caw.size(); caw.reset(); try { if (car != null) { car.reset(); } for (int i = 0; i < oldCawSize; ++i) caw.write(zeroArray); car = new CharArrayReader(caw.toCharArray()); car.close(); caw.reset(); caw = null; } catch (final IOException e) { throw new RuntimeException(e); } } } final String userName = userNameSB.toString(); // user-name is a unique client JVM identifier - not a real user name. final String pw = new String(password); if (AuthManager.getInstance().isPasswordValid(pw)) { requestContext.setSecurityContext(new SecurityContextImpl(userName, "https".equals(uriInfo.getRequestUri().getScheme()))); return; } throw newUnauthorizedException(); } public static class SecurityContextImpl implements SecurityContext { private final Principal principal; private final boolean secure; public SecurityContextImpl(final String userName, final boolean secure) { this.principal = new Principal() { @Override public String getName() { return userName; } }; this.secure = secure; } @Override public Principal getUserPrincipal() { return principal; } /** * @param role Role to be checked */ @Override public boolean isUserInRole(String role) { if ("admin".equals(role)) { return false; } else if ("user".equals(role)) { return principal != null; } return false; } @Override public boolean isSecure() { return secure; } @Override public String getAuthenticationScheme() { if (principal == null) { return null; } return SecurityContext.BASIC_AUTH; } } private WebApplicationException newUnauthorizedException() { return new NotAuthorizedException("Basic realm=\"CloudStoreServer.Local\""); } private byte[] getBasicAuthEncodedBA(final String basicAuthEncoded) { byte[] basicAuthDecodedBA; try { basicAuthDecodedBA = Base64.decode(basicAuthEncoded.getBytes(IOUtil.CHARSET_NAME_UTF_8)); } catch (final UnsupportedEncodingException e1) { throw new RuntimeException(e1); } return basicAuthDecodedBA; } }