/** * Add an authentication filter to the web application context if edison.ldap property is set to {@code enabled}'. * All routes starting with the value of the {@code edison.ldap.prefix} property will be secured by LDAP. If no * property is set this will default to all routes starting with '/internal'. * * @param ldapProperties the properties used to configure LDAP * @param ldapConnectionFactory the connection factory used to build the LdapAuthenticationFilter * @return FilterRegistrationBean */ @Bean public FilterRegistrationBean<LdapAuthenticationFilter> ldapAuthenticationFilter(final LdapProperties ldapProperties, final LdapConnectionFactory ldapConnectionFactory) { FilterRegistrationBean<LdapAuthenticationFilter> filterRegistration = new FilterRegistrationBean<>(); filterRegistration.setFilter(new LdapAuthenticationFilter(ldapProperties, ldapConnectionFactory)); filterRegistration.setOrder(Ordered.LOWEST_PRECEDENCE - 1); ldapProperties.getPrefixes().forEach(prefix -> filterRegistration.addUrlPatterns(String.format("%s/*", prefix))); return filterRegistration; }
private Optional<HttpServletRequest> tryToGetAuthenticatedRequest(final HttpServletRequest request, final Credentials credentials) { try (final LDAPConnection ldap = ldapConnectionFactory.buildLdapConnection()) { for (String baseDN : ldapProperties.getBaseDn()) { final String userDN = userDnFrom(credentials, baseDN); try { if (authenticate(ldap, userDN, credentials.getPassword())) { return ldapProperties.getRoleBaseDn() != null ? Optional.of(new LdapRoleCheckingRequest(request, ldap, userDN, ldapProperties)) : Optional.of(request); } } catch (LDAPBindException e) { LOG.debug("LDAPBindException for userDN: {}", userDN); } } LOG.warn("Could not bind to LDAP: {}", credentials.getUsername()); } catch (LDAPException | GeneralSecurityException e) { LOG.warn("Authentication error: ", e); } return Optional.empty(); }
@Test public void shouldNotApplyFilterToInternalJavascript() throws Exception { final HttpServletRequest request = requestWithoutAuthorizationHeader(); when(request.getServletPath()).thenReturn("/internal/js/foo.js"); final FilterChain filterChain = mock(FilterChain.class); testee.doFilter(request, response, filterChain); verify(filterChain).doFilter(request, response); }
@Override protected void doFilterInternal(final HttpServletRequest request, final HttpServletResponse response, final FilterChain filterChain) throws ServletException, IOException { Optional<Credentials> optionalCredentials = readFrom(request); if (optionalCredentials.isPresent()) { final Optional<HttpServletRequest> authRequest = tryToGetAuthenticatedRequest(request, optionalCredentials.get()); if (authRequest.isPresent()) { filterChain.doFilter(authRequest.get(), response); } else { unauthorized(response); } } else { unauthorized(response); } }
@Test public void shouldApplyFilterToAuthenticatedUser() throws IOException, ServletException, GeneralSecurityException, LDAPException { final LdapProperties ldapProperties = ldapProperties("someHost", 389, singletonList("someBaseDn"), null, "someRdnIdentifier", singletonList("/internal"), StartTLS, WHITELISTED_PATH); final LdapConnectionFactory connectionFactory = mock(LdapConnectionFactory.class); final LDAPConnection ldapConnection = someLdapConnectionReturning(SUCCESS); when(connectionFactory.buildLdapConnection()).thenReturn(ldapConnection); testee = new LdapAuthenticationFilter(ldapProperties, connectionFactory); final HttpServletRequest request = requestWithAuthorizationHeader(); when(request.getServletPath()).thenReturn("/foo"); final FilterChain filterChain = mock(FilterChain.class); testee.doFilter(request, response, filterChain); verify(filterChain).doFilter(any(HttpServletRequest.class), any(HttpServletResponse.class)); }
@Test public void shouldAuthenticateUser() throws LDAPException { final boolean authenticated = testee.authenticate(someLdapConnectionReturning(SUCCESS), "user", "password"); assertThat(authenticated).isEqualTo(true); }
@Test public void shouldBuildUserDnFromCredentials() { final String userDn = testee.userDnFrom(new Credentials("user", "password"), "someBaseDn"); assertThat(userDn).isEqualTo("someRdnIdentifier=user,someBaseDn"); }
@Test public void shouldNotApplyFilterToNotAuthenticatedUser() throws IOException, ServletException, GeneralSecurityException, LDAPException { final LdapProperties ldapProperties = ldapProperties("someHost", 389, singletonList("someBaseDn"), null, "someRdnIdentifier", singletonList("/internal"), StartTLS, WHITELISTED_PATH); final LdapConnectionFactory connectionFactory = mock(LdapConnectionFactory.class); final LDAPConnection ldapConnection = someLdapConnectionReturning(AUTHORIZATION_DENIED); when(connectionFactory.buildLdapConnection()).thenReturn(ldapConnection); testee = new LdapAuthenticationFilter(ldapProperties, connectionFactory); final HttpServletRequest request = requestWithAuthorizationHeader(); when(request.getServletPath()).thenReturn("/foo"); final FilterChain filterChain = mock(FilterChain.class); testee.doFilter(request, response, filterChain); verify(filterChain, never()).doFilter(any(HttpServletRequest.class), any(HttpServletResponse.class)); }
@Test public void shouldNotAuthenticateUser() throws LDAPException { final boolean authenticated = testee.authenticate(someLdapConnectionReturning(AUTHORIZATION_DENIED), "user", "password"); assertThat(authenticated).isEqualTo(false); }
@Test public void shouldApplyFilterToAuthenticatedUserWithAdditionallyConfiguredBaseDn() throws IOException, ServletException, GeneralSecurityException, LDAPException { // given final LdapProperties ldapProperties = ldapProperties("someHost", 389, asList("exceptionBaseDn", "successBaseDn"), null, "someRdnIdentifier", singletonList("/internal"), StartTLS, WHITELISTED_PATH); final LdapConnectionFactory connectionFactory = mock(LdapConnectionFactory.class); final LDAPConnection ldapConnection = someLdapConnectionReturningSuccessOrThrowingBindException("successBaseDn", "exceptionBaseDn"); when(connectionFactory.buildLdapConnection()).thenReturn(ldapConnection); testee = new LdapAuthenticationFilter(ldapProperties, connectionFactory); // when final HttpServletRequest request = requestWithAuthorizationHeader(); when(request.getServletPath()).thenReturn("/foo"); final FilterChain filterChain = mock(FilterChain.class); testee.doFilter(request, response, filterChain); // then verify(ldapConnection).bind(contains("exceptionBaseDn"), anyString()); verify(ldapConnection).bind(contains("successBaseDn"), anyString()); verify(filterChain).doFilter(any(HttpServletRequest.class), any(HttpServletResponse.class)); }
@BeforeEach public void setUp() { ldapConnectionFactory = mock(LdapConnectionFactory.class); response = mock(HttpServletResponse.class); testee = new LdapAuthenticationFilter( ldapProperties("someHost", 389, singletonList("someBaseDn"), null, "someRdnIdentifier", singletonList("/internal"), StartTLS, WHITELISTED_PATH), ldapConnectionFactory ); }
@Test public void shouldNotApplyFilterToWhitelistedEndpoint() throws Exception { final HttpServletRequest request = requestWithoutAuthorizationHeader(); when(request.getServletPath()).thenReturn(WHITELISTED_PATH + "/etc"); final FilterChain filterChain = mock(FilterChain.class); testee.doFilter(request, response, filterChain); verify(filterChain).doFilter(request, response); }
@Test public void shouldFailToStartIfHostIsNotConfigured() { assertThrows(IllegalStateException.class, () -> { new LdapAuthenticationFilter( ldapProperties("", 389, singletonList("someBaseDn"), null, "someRdnIdentifier", singletonList("/internal"), StartTLS), ldapConnectionFactory ); }); }
@Test public void shouldFailToStartIfAuthorizationHeaderIsMissing() throws Exception { testee.doFilter(requestWithoutAuthorizationHeader(), response, mock(FilterChain.class)); assertUnauthorized(); }
@Test public void shouldFailToStartIfRdnIdentifierIsNotConfigured() { assertThrows(IllegalStateException.class, () -> { new LdapAuthenticationFilter( ldapProperties("someHost", 389, singletonList("someBaseDn"), null, "", singletonList("/internal"), StartTLS), ldapConnectionFactory ); }); }
@Test public void shouldBeUnauthenticatedIfLdapConnectionFails() throws Exception { final LDAPConnection ldapConnection = someLdapConnectionReturning(SERVER_DOWN); when(ldapConnectionFactory.buildLdapConnection()).thenReturn(ldapConnection); testee.doFilter(requestWithAuthorizationHeader(), response, mock(FilterChain.class)); assertUnauthorized(); }
@Test public void shouldFailToStartIfBaseDnIsNotConfigured() { assertThrows(IllegalStateException.class, () -> { new LdapAuthenticationFilter( ldapProperties("someHost", 389, singletonList(""), null, "someRdnIdentifier", singletonList("/internal"), StartTLS), ldapConnectionFactory ); }); }