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 char[] ca = null; 062 CharArrayWriter caw = new CharArrayWriter(basicAuthDecodedBA.length + 1); 063 CharArrayReader car = null; 064 try { 065 final Reader r = new InputStreamReader(in, IOUtil.CHARSET_NAME_UTF_8); 066 int charsReadTotal = 0; 067 int charsRead; 068 do { 069 final char[] c = new char[10]; 070 charsRead = r.read(c); 071 caw.write(c); 072 073 if (charsRead > 0) 074 charsReadTotal += charsRead; 075 } while (charsRead >= 0); 076 077 charsRead = 0; 078 079 car = new CharArrayReader(ca = caw.toCharArray()); 080 int charsReadTotalCheck = 0; 081 082 while (charsRead >= 0 && charsRead < charsReadTotal) { 083 final char[] cbuf = new char[1]; 084 charsRead = car.read(cbuf); 085 if (charsRead > 0) 086 charsReadTotalCheck += charsRead; 087 088 if (cbuf[0] == ':') 089 break; 090 091 userNameSB.append(cbuf[0]); 092 } 093 094 if (charsRead >= 0 && charsRead < charsReadTotal) { 095 password = new char[charsReadTotal - charsReadTotalCheck]; 096 final int passwordSize = car.read(password); 097 if (passwordSize + charsReadTotalCheck != charsReadTotal) 098 throw new IllegalStateException("passwordSize and charsRead must match charsReadTotal!" 099 + " passwordSize=" + passwordSize 100 + ", charsRead=" + charsRead 101 + ", charsReadTotal=" + charsReadTotal);//TODO for testing 102 } 103 } catch (final Exception e) { 104 throw new WebApplicationException(Response.status(Status.INTERNAL_SERVER_ERROR).type(MediaType.APPLICATION_XML).entity(new Error(e)).build()); 105 } finally { 106 // For extra safety: Overwrite all sensitive memory with 0. 107 Arrays.fill(basicAuthDecodedBA, (byte)0); 108 109 if (ca != null) 110 Arrays.fill(ca, (char)0); 111 112 // overwrite caw: 113 if (caw != null) { 114 final char[] zeroArray = new char[caw.size()]; 115 caw.reset(); 116 try { 117 caw.write(zeroArray); 118 caw = null; 119 } catch (final IOException e) { 120 throw new RuntimeException(e); 121 } 122 } 123 } 124 125 final String userName = userNameSB.toString(); // user-name is a unique client JVM identifier - not a real user name. 126 boolean passwordValid = AuthManager.getInstance().isPasswordValid(password); 127 Arrays.fill(password, (char)0); // password is not needed anymore => clear it 128 if (passwordValid) { 129 requestContext.setSecurityContext(new SecurityContextImpl(userName, "https".equals(uriInfo.getRequestUri().getScheme()))); 130 return; 131 } 132 throw newUnauthorizedException(); 133 } 134 135 public static class SecurityContextImpl implements SecurityContext { 136 137 private final Principal principal; 138 private final boolean secure; 139 140 public SecurityContextImpl(final String userName, final boolean secure) { 141 this.principal = new Principal() { 142 @Override 143 public String getName() { 144 return userName; 145 } 146 }; 147 this.secure = secure; 148 } 149 150 @Override 151 public Principal getUserPrincipal() { 152 return principal; 153 } 154 155 /** 156 * @param role Role to be checked 157 */ 158 @Override 159 public boolean isUserInRole(String role) { 160 if ("admin".equals(role)) { 161 return false; 162 } else if ("user".equals(role)) { 163 return principal != null; 164 } 165 return false; 166 } 167 168 @Override 169 public boolean isSecure() { 170 return secure; 171 } 172 173 @Override 174 public String getAuthenticationScheme() { 175 if (principal == null) { 176 return null; 177 } 178 return SecurityContext.BASIC_AUTH; 179 } 180 } 181 182 private WebApplicationException newUnauthorizedException() { 183 return new NotAuthorizedException("Basic realm=\"CloudStoreServer.Local\""); 184 } 185 186 private byte[] getBasicAuthEncodedBA(final String basicAuthEncoded) { 187 byte[] basicAuthDecodedBA; 188 try { 189 basicAuthDecodedBA = Base64.decode(basicAuthEncoded.getBytes(IOUtil.CHARSET_NAME_UTF_8)); 190 } catch (final UnsupportedEncodingException e1) { 191 throw new RuntimeException(e1); 192 } 193 return basicAuthDecodedBA; 194 } 195 196}