From 22c65886237ed7c4e8cfa0aff95d751b6d70f7cc Mon Sep 17 00:00:00 2001 From: Alejandro Abdelnur Date: Tue, 12 Jun 2012 20:45:57 +0000 Subject: [PATCH] HADOOP-8458. Add management hook to AuthenticationHandler to enable delegation token operations support (tucu) git-svn-id: https://svn.apache.org/repos/asf/hadoop/common/trunk@1349514 13f79535-47bb-0310-9956-ffa450edef68 --- .../server/AuthenticationFilter.java | 76 ++++++----- .../server/AuthenticationHandler.java | 28 ++++ .../server/KerberosAuthenticationHandler.java | 21 +++ .../server/PseudoAuthenticationHandler.java | 21 +++ .../server/TestAuthenticationFilter.java | 127 ++++++++++++++++-- .../hadoop-common/CHANGES.txt | 3 + 6 files changed, 226 insertions(+), 50 deletions(-) diff --git a/hadoop-common-project/hadoop-auth/src/main/java/org/apache/hadoop/security/authentication/server/AuthenticationFilter.java b/hadoop-common-project/hadoop-auth/src/main/java/org/apache/hadoop/security/authentication/server/AuthenticationFilter.java index 28a4d3de90..b1795d1524 100644 --- a/hadoop-common-project/hadoop-auth/src/main/java/org/apache/hadoop/security/authentication/server/AuthenticationFilter.java +++ b/hadoop-common-project/hadoop-auth/src/main/java/org/apache/hadoop/security/authentication/server/AuthenticationFilter.java @@ -341,45 +341,49 @@ public class AuthenticationFilter implements Filter { LOG.warn("AuthenticationToken ignored: " + ex.getMessage()); token = null; } - if (token == null) { - if (LOG.isDebugEnabled()) { - LOG.debug("Request [{}] triggering authentication", getRequestURL(httpRequest)); + if (authHandler.managementOperation(token, httpRequest, httpResponse)) { + if (token == null) { + if (LOG.isDebugEnabled()) { + LOG.debug("Request [{}] triggering authentication", getRequestURL(httpRequest)); + } + token = authHandler.authenticate(httpRequest, httpResponse); + if (token != null && token != AuthenticationToken.ANONYMOUS) { + token.setExpires(System.currentTimeMillis() + getValidity() * 1000); + } + newToken = true; } - token = authHandler.authenticate(httpRequest, httpResponse); - if (token != null && token != AuthenticationToken.ANONYMOUS) { - token.setExpires(System.currentTimeMillis() + getValidity() * 1000); + if (token != null) { + unauthorizedResponse = false; + if (LOG.isDebugEnabled()) { + LOG.debug("Request [{}] user [{}] authenticated", getRequestURL(httpRequest), token.getUserName()); + } + final AuthenticationToken authToken = token; + httpRequest = new HttpServletRequestWrapper(httpRequest) { + + @Override + public String getAuthType() { + return authToken.getType(); + } + + @Override + public String getRemoteUser() { + return authToken.getUserName(); + } + + @Override + public Principal getUserPrincipal() { + return (authToken != AuthenticationToken.ANONYMOUS) ? authToken : null; + } + }; + if (newToken && token != AuthenticationToken.ANONYMOUS) { + String signedToken = signer.sign(token.toString()); + Cookie cookie = createCookie(signedToken); + httpResponse.addCookie(cookie); + } + filterChain.doFilter(httpRequest, httpResponse); } - newToken = true; - } - if (token != null) { + } else { unauthorizedResponse = false; - if (LOG.isDebugEnabled()) { - LOG.debug("Request [{}] user [{}] authenticated", getRequestURL(httpRequest), token.getUserName()); - } - final AuthenticationToken authToken = token; - httpRequest = new HttpServletRequestWrapper(httpRequest) { - - @Override - public String getAuthType() { - return authToken.getType(); - } - - @Override - public String getRemoteUser() { - return authToken.getUserName(); - } - - @Override - public Principal getUserPrincipal() { - return (authToken != AuthenticationToken.ANONYMOUS) ? authToken : null; - } - }; - if (newToken && token != AuthenticationToken.ANONYMOUS) { - String signedToken = signer.sign(token.toString()); - Cookie cookie = createCookie(signedToken); - httpResponse.addCookie(cookie); - } - filterChain.doFilter(httpRequest, httpResponse); } } catch (AuthenticationException ex) { unauthorizedMsg = ex.toString(); diff --git a/hadoop-common-project/hadoop-auth/src/main/java/org/apache/hadoop/security/authentication/server/AuthenticationHandler.java b/hadoop-common-project/hadoop-auth/src/main/java/org/apache/hadoop/security/authentication/server/AuthenticationHandler.java index 958680fcad..7cafe8bcbd 100644 --- a/hadoop-common-project/hadoop-auth/src/main/java/org/apache/hadoop/security/authentication/server/AuthenticationHandler.java +++ b/hadoop-common-project/hadoop-auth/src/main/java/org/apache/hadoop/security/authentication/server/AuthenticationHandler.java @@ -58,6 +58,34 @@ public interface AuthenticationHandler { */ public void destroy(); + /** + * Performs an authentication management operation. + *

+ * This is useful for handling operations like get/renew/cancel + * delegation tokens which are being handled as operations of the + * service end-point. + *

+ * If the method returns TRUE the request will continue normal + * processing, this means the method has not produced any HTTP response. + *

+ * If the method returns FALSE the request will end, this means + * the method has produced the corresponding HTTP response. + * + * @param token the authentication token if any, otherwise NULL. + * @param request the HTTP client request. + * @param response the HTTP client response. + * @return TRUE if the request should be processed as a regular + * request, + * FALSE otherwise. + * + * @throws IOException thrown if an IO error occurred. + * @throws AuthenticationException thrown if an Authentication error occurred. + */ + public boolean managementOperation(AuthenticationToken token, + HttpServletRequest request, + HttpServletResponse response) + throws IOException, AuthenticationException; + /** * Performs an authentication step for the given HTTP client request. *

diff --git a/hadoop-common-project/hadoop-auth/src/main/java/org/apache/hadoop/security/authentication/server/KerberosAuthenticationHandler.java b/hadoop-common-project/hadoop-auth/src/main/java/org/apache/hadoop/security/authentication/server/KerberosAuthenticationHandler.java index 8cad2cc220..07b64f48f9 100644 --- a/hadoop-common-project/hadoop-auth/src/main/java/org/apache/hadoop/security/authentication/server/KerberosAuthenticationHandler.java +++ b/hadoop-common-project/hadoop-auth/src/main/java/org/apache/hadoop/security/authentication/server/KerberosAuthenticationHandler.java @@ -232,6 +232,27 @@ public class KerberosAuthenticationHandler implements AuthenticationHandler { return keytab; } + /** + * This is an empty implementation, it always returns TRUE. + * + * + * + * @param token the authentication token if any, otherwise NULL. + * @param request the HTTP client request. + * @param response the HTTP client response. + * + * @return TRUE + * @throws IOException it is never thrown. + * @throws AuthenticationException it is never thrown. + */ + @Override + public boolean managementOperation(AuthenticationToken token, + HttpServletRequest request, + HttpServletResponse response) + throws IOException, AuthenticationException { + return true; + } + /** * It enforces the the Kerberos SPNEGO authentication sequence returning an {@link AuthenticationToken} only * after the Kerberos SPNEGO sequence has completed successfully. diff --git a/hadoop-common-project/hadoop-auth/src/main/java/org/apache/hadoop/security/authentication/server/PseudoAuthenticationHandler.java b/hadoop-common-project/hadoop-auth/src/main/java/org/apache/hadoop/security/authentication/server/PseudoAuthenticationHandler.java index 336c36e4d2..1a2f98c1c9 100644 --- a/hadoop-common-project/hadoop-auth/src/main/java/org/apache/hadoop/security/authentication/server/PseudoAuthenticationHandler.java +++ b/hadoop-common-project/hadoop-auth/src/main/java/org/apache/hadoop/security/authentication/server/PseudoAuthenticationHandler.java @@ -93,6 +93,27 @@ public class PseudoAuthenticationHandler implements AuthenticationHandler { return TYPE; } + /** + * This is an empty implementation, it always returns TRUE. + * + * + * + * @param token the authentication token if any, otherwise NULL. + * @param request the HTTP client request. + * @param response the HTTP client response. + * + * @return TRUE + * @throws IOException it is never thrown. + * @throws AuthenticationException it is never thrown. + */ + @Override + public boolean managementOperation(AuthenticationToken token, + HttpServletRequest request, + HttpServletResponse response) + throws IOException, AuthenticationException { + return true; + } + /** * Authenticates an HTTP client request. *

diff --git a/hadoop-common-project/hadoop-auth/src/test/java/org/apache/hadoop/security/authentication/server/TestAuthenticationFilter.java b/hadoop-common-project/hadoop-auth/src/test/java/org/apache/hadoop/security/authentication/server/TestAuthenticationFilter.java index 4f1bc111a7..746cf7b089 100644 --- a/hadoop-common-project/hadoop-auth/src/test/java/org/apache/hadoop/security/authentication/server/TestAuthenticationFilter.java +++ b/hadoop-common-project/hadoop-auth/src/test/java/org/apache/hadoop/security/authentication/server/TestAuthenticationFilter.java @@ -71,6 +71,7 @@ public class TestAuthenticationFilter extends TestCase { public static class DummyAuthenticationHandler implements AuthenticationHandler { public static boolean init; + public static boolean managementOperationReturn; public static boolean destroy; public static final String TYPE = "dummy"; @@ -83,6 +84,19 @@ public class TestAuthenticationFilter extends TestCase { @Override public void init(Properties config) throws ServletException { init = true; + managementOperationReturn = + config.getProperty("management.operation.return", "true").equals("true"); + } + + @Override + public boolean managementOperation(AuthenticationToken token, + HttpServletRequest request, + HttpServletResponse response) + throws IOException, AuthenticationException { + if (!managementOperationReturn) { + response.setStatus(HttpServletResponse.SC_ACCEPTED); + } + return managementOperationReturn; } @Override @@ -170,10 +184,14 @@ public class TestAuthenticationFilter extends TestCase { filter = new AuthenticationFilter(); try { FilterConfig config = Mockito.mock(FilterConfig.class); + Mockito.when(config.getInitParameter("management.operation.return")). + thenReturn("true"); Mockito.when(config.getInitParameter(AuthenticationFilter.AUTH_TYPE)).thenReturn( DummyAuthenticationHandler.class.getName()); Mockito.when(config.getInitParameterNames()).thenReturn( - new Vector(Arrays.asList(AuthenticationFilter.AUTH_TYPE)).elements()); + new Vector( + Arrays.asList(AuthenticationFilter.AUTH_TYPE, + "management.operation.return")).elements()); filter.init(config); assertTrue(DummyAuthenticationHandler.init); } finally { @@ -201,10 +219,14 @@ public class TestAuthenticationFilter extends TestCase { AuthenticationFilter filter = new AuthenticationFilter(); try { FilterConfig config = Mockito.mock(FilterConfig.class); + Mockito.when(config.getInitParameter("management.operation.return")). + thenReturn("true"); Mockito.when(config.getInitParameter(AuthenticationFilter.AUTH_TYPE)).thenReturn( DummyAuthenticationHandler.class.getName()); Mockito.when(config.getInitParameterNames()).thenReturn( - new Vector(Arrays.asList(AuthenticationFilter.AUTH_TYPE)).elements()); + new Vector( + Arrays.asList(AuthenticationFilter.AUTH_TYPE, + "management.operation.return")).elements()); filter.init(config); HttpServletRequest request = Mockito.mock(HttpServletRequest.class); @@ -221,12 +243,16 @@ public class TestAuthenticationFilter extends TestCase { AuthenticationFilter filter = new AuthenticationFilter(); try { FilterConfig config = Mockito.mock(FilterConfig.class); + Mockito.when(config.getInitParameter("management.operation.return")). + thenReturn("true"); Mockito.when(config.getInitParameter(AuthenticationFilter.AUTH_TYPE)).thenReturn( DummyAuthenticationHandler.class.getName()); Mockito.when(config.getInitParameter(AuthenticationFilter.SIGNATURE_SECRET)).thenReturn("secret"); Mockito.when(config.getInitParameterNames()).thenReturn( - new Vector(Arrays.asList(AuthenticationFilter.AUTH_TYPE, - AuthenticationFilter.SIGNATURE_SECRET)).elements()); + new Vector( + Arrays.asList(AuthenticationFilter.AUTH_TYPE, + AuthenticationFilter.SIGNATURE_SECRET, + "management.operation.return")).elements()); filter.init(config); AuthenticationToken token = new AuthenticationToken("u", "p", DummyAuthenticationHandler.TYPE); @@ -250,12 +276,15 @@ public class TestAuthenticationFilter extends TestCase { AuthenticationFilter filter = new AuthenticationFilter(); try { FilterConfig config = Mockito.mock(FilterConfig.class); + Mockito.when(config.getInitParameter("management.operation.return")).thenReturn("true"); Mockito.when(config.getInitParameter(AuthenticationFilter.AUTH_TYPE)).thenReturn( DummyAuthenticationHandler.class.getName()); Mockito.when(config.getInitParameter(AuthenticationFilter.SIGNATURE_SECRET)).thenReturn("secret"); Mockito.when(config.getInitParameterNames()).thenReturn( - new Vector(Arrays.asList(AuthenticationFilter.AUTH_TYPE, - AuthenticationFilter.SIGNATURE_SECRET)).elements()); + new Vector( + Arrays.asList(AuthenticationFilter.AUTH_TYPE, + AuthenticationFilter.SIGNATURE_SECRET, + "management.operation.return")).elements()); filter.init(config); AuthenticationToken token = new AuthenticationToken("u", "p", "invalidtype"); @@ -284,12 +313,16 @@ public class TestAuthenticationFilter extends TestCase { AuthenticationFilter filter = new AuthenticationFilter(); try { FilterConfig config = Mockito.mock(FilterConfig.class); + Mockito.when(config.getInitParameter("management.operation.return")). + thenReturn("true"); Mockito.when(config.getInitParameter(AuthenticationFilter.AUTH_TYPE)).thenReturn( DummyAuthenticationHandler.class.getName()); Mockito.when(config.getInitParameter(AuthenticationFilter.SIGNATURE_SECRET)).thenReturn("secret"); Mockito.when(config.getInitParameterNames()).thenReturn( - new Vector(Arrays.asList(AuthenticationFilter.AUTH_TYPE, - AuthenticationFilter.SIGNATURE_SECRET)).elements()); + new Vector( + Arrays.asList(AuthenticationFilter.AUTH_TYPE, + AuthenticationFilter.SIGNATURE_SECRET, + "management.operation.return")).elements()); filter.init(config); AuthenticationToken token = new AuthenticationToken("u", "p", "invalidtype"); @@ -318,10 +351,14 @@ public class TestAuthenticationFilter extends TestCase { AuthenticationFilter filter = new AuthenticationFilter(); try { FilterConfig config = Mockito.mock(FilterConfig.class); + Mockito.when(config.getInitParameter("management.operation.return")). + thenReturn("true"); Mockito.when(config.getInitParameter(AuthenticationFilter.AUTH_TYPE)).thenReturn( DummyAuthenticationHandler.class.getName()); Mockito.when(config.getInitParameterNames()).thenReturn( - new Vector(Arrays.asList(AuthenticationFilter.AUTH_TYPE)).elements()); + new Vector( + Arrays.asList(AuthenticationFilter.AUTH_TYPE, + "management.operation.return")).elements()); filter.init(config); HttpServletRequest request = Mockito.mock(HttpServletRequest.class); @@ -353,6 +390,8 @@ public class TestAuthenticationFilter extends TestCase { AuthenticationFilter filter = new AuthenticationFilter(); try { FilterConfig config = Mockito.mock(FilterConfig.class); + Mockito.when(config.getInitParameter("management.operation.return")). + thenReturn("true"); Mockito.when(config.getInitParameter(AuthenticationFilter.AUTH_TYPE)).thenReturn( DummyAuthenticationHandler.class.getName()); Mockito.when(config.getInitParameter(AuthenticationFilter.AUTH_TOKEN_VALIDITY)).thenReturn("1000"); @@ -360,7 +399,8 @@ public class TestAuthenticationFilter extends TestCase { Mockito.when(config.getInitParameterNames()).thenReturn( new Vector(Arrays.asList(AuthenticationFilter.AUTH_TYPE, AuthenticationFilter.AUTH_TOKEN_VALIDITY, - AuthenticationFilter.SIGNATURE_SECRET)).elements()); + AuthenticationFilter.SIGNATURE_SECRET, + "management.operation.return")).elements()); if (withDomainPath) { Mockito.when(config.getInitParameter(AuthenticationFilter.COOKIE_DOMAIN)).thenReturn(".foo.com"); @@ -370,7 +410,8 @@ public class TestAuthenticationFilter extends TestCase { AuthenticationFilter.AUTH_TOKEN_VALIDITY, AuthenticationFilter.SIGNATURE_SECRET, AuthenticationFilter.COOKIE_DOMAIN, - AuthenticationFilter.COOKIE_PATH)).elements()); + AuthenticationFilter.COOKIE_PATH, + "management.operation.return")).elements()); } filter.init(config); @@ -458,10 +499,14 @@ public class TestAuthenticationFilter extends TestCase { AuthenticationFilter filter = new AuthenticationFilter(); try { FilterConfig config = Mockito.mock(FilterConfig.class); + Mockito.when(config.getInitParameter("management.operation.return")). + thenReturn("true"); Mockito.when(config.getInitParameter(AuthenticationFilter.AUTH_TYPE)).thenReturn( DummyAuthenticationHandler.class.getName()); Mockito.when(config.getInitParameterNames()).thenReturn( - new Vector(Arrays.asList(AuthenticationFilter.AUTH_TYPE)).elements()); + new Vector( + Arrays.asList(AuthenticationFilter.AUTH_TYPE, + "management.operation.return")).elements()); filter.init(config); HttpServletRequest request = Mockito.mock(HttpServletRequest.class); @@ -503,10 +548,14 @@ public class TestAuthenticationFilter extends TestCase { AuthenticationFilter filter = new AuthenticationFilter(); try { FilterConfig config = Mockito.mock(FilterConfig.class); + Mockito.when(config.getInitParameter("management.operation.return")). + thenReturn("true"); Mockito.when(config.getInitParameter(AuthenticationFilter.AUTH_TYPE)).thenReturn( DummyAuthenticationHandler.class.getName()); Mockito.when(config.getInitParameterNames()).thenReturn( - new Vector(Arrays.asList(AuthenticationFilter.AUTH_TYPE)).elements()); + new Vector( + Arrays.asList(AuthenticationFilter.AUTH_TYPE, + "management.operation.return")).elements()); filter.init(config); HttpServletRequest request = Mockito.mock(HttpServletRequest.class); @@ -563,10 +612,14 @@ public class TestAuthenticationFilter extends TestCase { AuthenticationFilter filter = new AuthenticationFilter(); try { FilterConfig config = Mockito.mock(FilterConfig.class); + Mockito.when(config.getInitParameter("management.operation.return")). + thenReturn("true"); Mockito.when(config.getInitParameter(AuthenticationFilter.AUTH_TYPE)).thenReturn( DummyAuthenticationHandler.class.getName()); Mockito.when(config.getInitParameterNames()).thenReturn( - new Vector(Arrays.asList(AuthenticationFilter.AUTH_TYPE)).elements()); + new Vector( + Arrays.asList(AuthenticationFilter.AUTH_TYPE, + "management.operation.return")).elements()); filter.init(config); HttpServletRequest request = Mockito.mock(HttpServletRequest.class); @@ -618,4 +671,50 @@ public class TestAuthenticationFilter extends TestCase { } } + public void testManagementOperation() throws Exception { + AuthenticationFilter filter = new AuthenticationFilter(); + try { + FilterConfig config = Mockito.mock(FilterConfig.class); + Mockito.when(config.getInitParameter("management.operation.return")). + thenReturn("false"); + Mockito.when(config.getInitParameter(AuthenticationFilter.AUTH_TYPE)). + thenReturn(DummyAuthenticationHandler.class.getName()); + Mockito.when(config.getInitParameterNames()).thenReturn( + new Vector( + Arrays.asList(AuthenticationFilter.AUTH_TYPE, + "management.operation.return")).elements()); + filter.init(config); + + HttpServletRequest request = Mockito.mock(HttpServletRequest.class); + Mockito.when(request.getRequestURL()). + thenReturn(new StringBuffer("http://foo:8080/bar")); + + HttpServletResponse response = Mockito.mock(HttpServletResponse.class); + + FilterChain chain = Mockito.mock(FilterChain.class); + + filter.doFilter(request, response, chain); + Mockito.verify(response).setStatus(HttpServletResponse.SC_ACCEPTED); + Mockito.verifyNoMoreInteractions(response); + + Mockito.reset(request); + Mockito.reset(response); + + AuthenticationToken token = new AuthenticationToken("u", "p", "t"); + token.setExpires(System.currentTimeMillis() + 1000); + Signer signer = new Signer("secret".getBytes()); + String tokenSigned = signer.sign(token.toString()); + Cookie cookie = new Cookie(AuthenticatedURL.AUTH_COOKIE, tokenSigned); + Mockito.when(request.getCookies()).thenReturn(new Cookie[]{cookie}); + + filter.doFilter(request, response, chain); + + Mockito.verify(response).setStatus(HttpServletResponse.SC_ACCEPTED); + Mockito.verifyNoMoreInteractions(response); + + } finally { + filter.destroy(); + } + } + } diff --git a/hadoop-common-project/hadoop-common/CHANGES.txt b/hadoop-common-project/hadoop-common/CHANGES.txt index 7b9332d954..6b80bf8244 100644 --- a/hadoop-common-project/hadoop-common/CHANGES.txt +++ b/hadoop-common-project/hadoop-common/CHANGES.txt @@ -174,6 +174,9 @@ Branch-2 ( Unreleased changes ) HADOOP-8135. Add ByteBufferReadable interface to FSDataInputStream. (Henry Robinson via atm) + HADOOP-8458. Add management hook to AuthenticationHandler to enable + delegation token operations support (tucu) + IMPROVEMENTS HADOOP-8340. SNAPSHOT build versions should compare as less than their eventual