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}