package de.otto.edison.authentication;
import com.unboundid.ldap.sdk.*;
import com.unboundid.ldap.sdk.extensions.StartTLSExtendedRequest;
import com.unboundid.util.ssl.SSLUtil;
import de.otto.edison.authentication.configuration.LdapProperties;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.web.filter.OncePerRequestFilter;
import javax.net.ssl.SSLContext;
import javax.servlet.FilterChain;
import javax.servlet.ServletException;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;
import java.io.IOException;
import java.security.GeneralSecurityException;
import java.util.Optional;
import static org.springframework.http.HttpHeaders.WWW_AUTHENTICATE;
import static org.springframework.http.HttpStatus.UNAUTHORIZED;
import static org.springframework.util.StringUtils.isEmpty;
/**
* Filter that checks for LDAP authentication once per request. Will not filter routes starting with
* {@link LdapProperties#whitelistedPaths}. Uses {@link LdapProperties} to create an SSL based connection to the
* configured LDAP server. Rejects requests with {@code HTTP 401} if authorization fails.
*/
public class LdapAuthenticationFilter extends OncePerRequestFilter {
private static Logger LOG = LoggerFactory.getLogger(LdapAuthenticationFilter.class);
private final LdapProperties ldapProperties;
public LdapAuthenticationFilter(final LdapProperties ldapProperties) {
this.ldapProperties = ldapProperties;
}
@Override
protected boolean shouldNotFilter(HttpServletRequest request) throws ServletException {
return ldapProperties.getWhitelistedPaths()
.stream()
.anyMatch(whitelistedPath -> request.getServletPath().startsWith(whitelistedPath));
}
@Override
protected void doFilterInternal(HttpServletRequest request, HttpServletResponse response, FilterChain filterChain) throws ServletException, IOException {
if (isEmpty(request.getHeader("Authorization"))) {
unauthorized(response);
} else {
Optional<Credentials> credentials = Credentials.readFrom(request);
if (!ldapProperties.isValid() || !credentials.isPresent() || !ldapAuthentication(credentials.get())) {
unauthorized(response);
} else {
filterChain.doFilter(request, response);
}
}
}
private void unauthorized(HttpServletResponse httpResponse) {
httpResponse.addHeader(WWW_AUTHENTICATE, "Basic realm=Authorization Required");
httpResponse.setStatus(UNAUTHORIZED.value());
}
private boolean ldapAuthentication(Credentials credentials) {
boolean authOK = false;
LDAPConnection ldapConnection = null;
try {
SSLUtil sslUtil = new SSLUtil();
SSLContext context = sslUtil.createSSLContext();
ExtendedRequest extRequest = new StartTLSExtendedRequest(context);
ldapConnection = new LDAPConnection(ldapProperties.getHost(), ldapProperties.getPort());
ldapConnection.processExtendedOperation(extRequest);
BindResult bindResult = ldapConnection.bind(
ldapProperties.getRdnIdentifier() + "=" + credentials.getUsername() + "," +
ldapProperties.getBaseDn(),
credentials.getPassword()
);
if (bindResult.getResultCode().equals(ResultCode.SUCCESS)) {
LOG.info("Login successful: " + credentials.getUsername());
authOK = true;
} else {
LOG.info("Access denied: " + credentials.getUsername());
}
} catch (LDAPException | GeneralSecurityException e) {
LOG.info("Authentication error: ", e);
} finally {
if (ldapConnection != null) {
ldapConnection.close();
}
}
return authOK;
}
}