From 14801e87b6c2d20285433ff4561c72ada12cbb42 Mon Sep 17 00:00:00 2001 From: Marc CARRE Date: Wed, 25 Jan 2017 20:00:50 +0000 Subject: [PATCH] Add dependency injection of PAM service (+tests +doc). --- README.md | 16 ++++++ .../servlet/PamAuthFilter.java | 51 ++++++++++++++----- .../servlet/PamAuthFilterTest.java | 12 +++-- 3 files changed, 62 insertions(+), 17 deletions(-) diff --git a/README.md b/README.md index 9847def..74e5983 100644 --- a/README.md +++ b/README.md @@ -16,6 +16,12 @@ It enables users to login using their Linux username and password. ## Usage: - add the JAR to your classpath, and +- optionally, create `/etc/pam.d/{application}` and configure it to be able to authenticate using PAM: + + # PAM configuration for {application} + # Standard Un*x authentication. + @include common-auth + - optionally, and depending your web application, add the filter to your `web.xml`: @@ -32,6 +38,10 @@ It enables users to login using their Linux username and password. realm NameOfYourApplication + + service + {application} + @@ -78,3 +88,9 @@ Apache License Version 2.0 See also: - [Sonatype's Gradle documentation](http://central.sonatype.org/pages/gradle.html) - [Gradle Nexus Staging plugin's documentation](https://github.com/Codearte/gradle-nexus-staging-plugin/) + +## Resources: + +- http://tldp.org/HOWTO/User-Authentication-HOWTO/x115.html +- http://www.linux-pam.org/Linux-PAM-html/sag-overview.html +- http://www.linux-pam.org/Linux-PAM-html/sag-configuration.html diff --git a/src/main/java/com/carmatechnologies/servlet/PamAuthFilter.java b/src/main/java/com/carmatechnologies/servlet/PamAuthFilter.java index 677d952..d0e1d16 100644 --- a/src/main/java/com/carmatechnologies/servlet/PamAuthFilter.java +++ b/src/main/java/com/carmatechnologies/servlet/PamAuthFilter.java @@ -15,6 +15,7 @@ import java.io.IOException; import java.nio.charset.Charset; import java.util.Base64; +import java.util.function.Function; import java.util.logging.Logger; import static java.lang.Character.isWhitespace; @@ -28,8 +29,21 @@ * @author Marc CARRE (@marccarre / carre.marc@gmail.com) */ public class PamAuthFilter implements Filter { - private static final String LOGIN = "login"; // PAM service we use to authenticate. See: http://tldp.org/HOWTO/User-Authentication-HOWTO/x115.html - private static final String REALM = "realm"; + /** + * Basic Authentication Scheme's realm. + * See also: https://tools.ietf.org/html/rfc2617#section-2 + */ + public static final String REALM = "realm"; + + /** + * PAM service used to authenticate. + * See: + * - http://tldp.org/HOWTO/User-Authentication-HOWTO/x115.html + * - http://www.linux-pam.org/Linux-PAM-html/sag-overview.html + * - http://www.linux-pam.org/Linux-PAM-html/sag-configuration.html + */ + public static final String SERVICE = "service"; + private static final String WHITESPACE = " "; private static final String COLON = ":"; private static final int AT_MOST_ONCE = 2; @@ -50,35 +64,44 @@ public class PamAuthFilter implements Filter { private static final Logger logger = Logger.getLogger(PamAuthFilter.class.getSimpleName()); - private final PAM pam; + private final Function pamFactory; private String realm; + private String service; + private PAM pam; public PamAuthFilter() { - this(newPam()); + this(PamAuthFilter::newPam); } - PamAuthFilter(final PAM pam) { - if (pam == null) { - throw new NullPointerException("Please provide a non-null value for PAM authenticator."); - } - this.pam = pam; - } - - private static PAM newPam() { + private static PAM newPam(final String service) { try { - return new PAM(LOGIN); + return new PAM(service); } catch (final PAMException e) { throw new RuntimeException(e); } } - String realm() { + PamAuthFilter(final Function pamFactory) { + if (pamFactory == null) { + throw new NullPointerException("Please provide a non-null PAM factory."); + } + this.pamFactory = pamFactory; + } + + public String realm() { return realm; } + public String service() { + return service; + } + @Override public void init(final FilterConfig config) throws ServletException { realm = checkNotBlank(config.getInitParameter(REALM), REALM); + service = checkNotBlank(config.getInitParameter(SERVICE), SERVICE); + logger.info(format("PAM authentication filter configured with %s=[%s] and %s=[%s].", REALM, realm, SERVICE, service)); + pam = pamFactory.apply(service); } private String checkNotBlank(final String value, final String name) throws ServletException { diff --git a/src/test/java/com/carmatechnologies/servlet/PamAuthFilterTest.java b/src/test/java/com/carmatechnologies/servlet/PamAuthFilterTest.java index 9550441..96bd65f 100644 --- a/src/test/java/com/carmatechnologies/servlet/PamAuthFilterTest.java +++ b/src/test/java/com/carmatechnologies/servlet/PamAuthFilterTest.java @@ -41,7 +41,7 @@ public class PamAuthFilterTest { private final FilterChain filterChain = mock(FilterChain.class); private final FilterConfig filterConfig = mock(FilterConfig.class); private final PAM pam = mock(PAM.class); - private final PamAuthFilter filter = new PamAuthFilter(pam); + private final PamAuthFilter filter = new PamAuthFilter((String) -> pam); @Rule public final ExpectedException exception = ExpectedException.none(); @@ -50,6 +50,7 @@ public class PamAuthFilterTest { public void setUp() throws ServletException { when(request.getRemoteAddr()).thenReturn("127.0.0.1"); when(filterConfig.getInitParameter("realm")).thenReturn("Tatooine"); + when(filterConfig.getInitParameter("service")).thenReturn("pam-servlet-filter"); filter.init(filterConfig); } @@ -156,9 +157,9 @@ public void malformedAuthorizationHeaderShouldReturnError401_BasicCredentialsWit } @Test - public void creatingFilterWithNullPamThrowsNullPointerException() { + public void creatingFilterWithNullPamFactoryThrowsNullPointerException() { exception.expect(NullPointerException.class); - exception.expectMessage(equalTo("Please provide a non-null value for PAM authenticator.")); + exception.expectMessage(equalTo("Please provide a non-null PAM factory.")); new PamAuthFilter(null); } @@ -167,6 +168,11 @@ public void initFilterShouldSetRealmToTheProvidedValue() throws ServletException assertThat(filter.realm(), is("Tatooine")); } + @Test + public void initFilterShouldSetPamServiceToTheProvidedValue() throws ServletException { + assertThat(filter.service(), is("pam-servlet-filter")); + } + @Test public void initFilterWithNullRealmThrowsServletException() throws ServletException { when(filterConfig.getInitParameter("realm")).thenReturn(null);