001package co.codewizards.cloudstore.ls.rest.server.auth; 002 003import co.codewizards.cloudstore.core.io.ByteArrayInputStream; 004import java.io.CharArrayReader; 005import java.io.CharArrayWriter; 006import java.io.IOException; 007import java.io.InputStreamReader; 008import java.io.Reader; 009import java.io.UnsupportedEncodingException; 010import java.security.Principal; 011import java.util.Arrays; 012 013import javax.servlet.http.HttpServletRequest; 014import javax.ws.rs.NotAuthorizedException; 015import javax.ws.rs.WebApplicationException; 016import javax.ws.rs.container.ContainerRequestContext; 017import javax.ws.rs.container.ContainerRequestFilter; 018import javax.ws.rs.core.Context; 019import javax.ws.rs.core.MediaType; 020import javax.ws.rs.core.Response; 021import javax.ws.rs.core.Response.Status; 022import javax.ws.rs.core.SecurityContext; 023import javax.ws.rs.core.UriInfo; 024 025import org.glassfish.jersey.internal.util.Base64; 026import org.slf4j.Logger; 027import org.slf4j.LoggerFactory; 028 029import co.codewizards.cloudstore.core.dto.Error; 030import co.codewizards.cloudstore.core.util.IOUtil; 031 032public class AuthFilter implements ContainerRequestFilter { 033 034 private static final Logger logger = LoggerFactory.getLogger(AuthFilter.class); 035 036 protected @Context UriInfo uriInfo; 037 038 protected @Context HttpServletRequest request; 039 040 @Override 041 public void filter(ContainerRequestContext requestContext) throws IOException { 042 final String authorizationHeader = request.getHeader("Authorization"); 043 if (authorizationHeader == null || authorizationHeader.isEmpty()) { 044 logger.debug("getAuth: There is no 'Authorization' header. Replying with a Status.UNAUTHORIZED response asking for 'Basic' authentication."); 045 throw newUnauthorizedException(); 046 } 047 048 logger.debug("getAuth: 'Authorization' header: {}", authorizationHeader); 049 050 if (!authorizationHeader.startsWith("Basic")) 051 throw new WebApplicationException(Response.status(Status.FORBIDDEN) 052 .type(MediaType.APPLICATION_XML) 053 .entity(new Error("Only 'Basic' authentication is supported!")).build()); 054 055 final String basicAuthEncoded = authorizationHeader.substring("Basic".length()).trim(); 056 final byte[] basicAuthDecodedBA = getBasicAuthEncodedBA(basicAuthEncoded); 057 final StringBuilder userNameSB = new StringBuilder(); 058 char[] password = null; 059 060 final ByteArrayInputStream in = new ByteArrayInputStream(basicAuthDecodedBA); 061 CharArrayWriter caw = new CharArrayWriter(basicAuthDecodedBA.length + 1); 062 CharArrayReader car = null; 063 try { 064 final Reader r = new InputStreamReader(in, IOUtil.CHARSET_NAME_UTF_8); 065 int charsReadTotal = 0; 066 int charsRead; 067 do { 068 final char[] c = new char[10]; 069 charsRead = r.read(c); 070 caw.write(c); 071 072 if (charsRead > 0) 073 charsReadTotal += charsRead; 074 } while (charsRead >= 0); 075 076 charsRead = 0; 077 078 car = new CharArrayReader(caw.toCharArray()); 079 int charsReadTotalCheck = 0; 080 081 while (charsRead >= 0 && charsRead < charsReadTotal) { 082 final char[] cbuf = new char[1]; 083 charsRead = car.read(cbuf); 084 if (charsRead > 0) 085 charsReadTotalCheck += charsRead; 086 087 if (cbuf[0] == ':') 088 break; 089 090 userNameSB.append(cbuf[0]); 091 } 092 093 if (charsRead >= 0 && charsRead < charsReadTotal) { 094 password = new char[charsReadTotal - charsReadTotalCheck]; 095 final int passwordSize = car.read(password); 096 if (passwordSize + charsReadTotalCheck != charsReadTotal) 097 throw new IllegalStateException("passwordSize and charsRead must match charsReadTotal!" 098 + " passwordSize=" + passwordSize 099 + ", charsRead=" + charsRead 100 + ", charsReadTotal=" + charsReadTotal);//TODO for testing 101 } 102 } catch (final Exception e) { 103 throw new WebApplicationException(Response.status(Status.INTERNAL_SERVER_ERROR).type(MediaType.APPLICATION_XML).entity(new Error(e)).build()); 104 } finally { 105 // For extra safety: Overwrite all sensitive memory with 0. 106 Arrays.fill(basicAuthDecodedBA, (byte)0); 107 108 final char[] zeroArray = new char[] {0}; 109 // overwrite caw & car: 110 if (caw != null) { 111 final int oldCawSize = caw.size(); 112 caw.reset(); 113 try { 114 if (car != null) { 115 car.reset(); 116 } 117 for (int i = 0; i < oldCawSize; ++i) 118 caw.write(zeroArray); 119 car = new CharArrayReader(caw.toCharArray()); 120 car.close(); 121 caw.reset(); 122 caw = null; 123 } catch (final IOException e) { 124 throw new RuntimeException(e); 125 } 126 } 127 } 128 129 final String userName = userNameSB.toString(); // user-name is a unique client JVM identifier - not a real user name. 130 final String pw = new String(password); 131 if (AuthManager.getInstance().isPasswordValid(pw)) { 132 requestContext.setSecurityContext(new SecurityContextImpl(userName, "https".equals(uriInfo.getRequestUri().getScheme()))); 133 return; 134 } 135 throw newUnauthorizedException(); 136 } 137 138 public static class SecurityContextImpl implements SecurityContext { 139 140 private final Principal principal; 141 private final boolean secure; 142 143 public SecurityContextImpl(final String userName, final boolean secure) { 144 this.principal = new Principal() { 145 @Override 146 public String getName() { 147 return userName; 148 } 149 }; 150 this.secure = secure; 151 } 152 153 @Override 154 public Principal getUserPrincipal() { 155 return principal; 156 } 157 158 /** 159 * @param role Role to be checked 160 */ 161 @Override 162 public boolean isUserInRole(String role) { 163 if ("admin".equals(role)) { 164 return false; 165 } else if ("user".equals(role)) { 166 return principal != null; 167 } 168 return false; 169 } 170 171 @Override 172 public boolean isSecure() { 173 return secure; 174 } 175 176 @Override 177 public String getAuthenticationScheme() { 178 if (principal == null) { 179 return null; 180 } 181 return SecurityContext.BASIC_AUTH; 182 } 183 } 184 185 private WebApplicationException newUnauthorizedException() { 186 return new NotAuthorizedException("Basic realm=\"CloudStoreServer.Local\""); 187 } 188 189 private byte[] getBasicAuthEncodedBA(final String basicAuthEncoded) { 190 byte[] basicAuthDecodedBA; 191 try { 192 basicAuthDecodedBA = Base64.decode(basicAuthEncoded.getBytes(IOUtil.CHARSET_NAME_UTF_8)); 193 } catch (final UnsupportedEncodingException e1) { 194 throw new RuntimeException(e1); 195 } 196 return basicAuthDecodedBA; 197 } 198 199}