Skip to content

Commit

Permalink
Add dependency injection of PAM service (+tests +doc).
Browse files Browse the repository at this point in the history
  • Loading branch information
marccarre committed Jan 25, 2017
1 parent 2a3b8b2 commit 14801e8
Show file tree
Hide file tree
Showing 3 changed files with 62 additions and 17 deletions.
16 changes: 16 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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`:

<?xml version="1.0" encoding="UTF-8"?>
Expand All @@ -32,6 +38,10 @@ It enables users to login using their Linux username and password.
<param-name>realm</param-name>
<param-value>NameOfYourApplication</param-value>
</init-param>
<init-param>
<param-name>service</param-name>
<param-value>{application}</param-value>
</init-param>
</filter>
<filter-mapping>
Expand Down Expand Up @@ -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
51 changes: 37 additions & 14 deletions src/main/java/com/carmatechnologies/servlet/PamAuthFilter.java
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand All @@ -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<String, PAM> 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<String, PAM> 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 {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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();
Expand All @@ -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);
}

Expand Down Expand Up @@ -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);
}

Expand All @@ -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);
Expand Down

0 comments on commit 14801e8

Please sign in to comment.