LdaptiveUserDetailsAutoConfiguration.java

/*
 * Copyright 2021 the original author or authors.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *      http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.bremersee.security.authentication;

import lombok.extern.slf4j.Slf4j;
import org.bremersee.data.ldaptive.LdaptiveAutoConfiguration;
import org.bremersee.data.ldaptive.LdaptiveOperations;
import org.bremersee.data.ldaptive.LdaptiveProperties;
import org.bremersee.data.ldaptive.LdaptiveProperties.UserDetailsProperties;
import org.bremersee.data.ldaptive.reactive.ReactiveLdaptiveOperations;
import org.bremersee.security.core.userdetails.LdaptivePasswordEncoder;
import org.bremersee.security.core.userdetails.LdaptivePasswordMatcher;
import org.bremersee.security.core.userdetails.LdaptiveUserDetailsService;
import org.bremersee.security.core.userdetails.ReactiveLdaptiveUserDetailsService;
import org.ldaptive.ConnectionFactory;
import org.springframework.beans.factory.ObjectProvider;
import org.springframework.boot.autoconfigure.AutoConfigureAfter;
import org.springframework.boot.autoconfigure.AutoConfigureBefore;
import org.springframework.boot.autoconfigure.condition.ConditionalOnClass;
import org.springframework.boot.autoconfigure.condition.ConditionalOnMissingBean;
import org.springframework.boot.autoconfigure.condition.ConditionalOnProperty;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication;
import org.springframework.boot.autoconfigure.condition.ConditionalOnWebApplication.Type;
import org.springframework.boot.context.event.ApplicationReadyEvent;
import org.springframework.boot.context.properties.EnableConfigurationProperties;
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;
import org.springframework.context.event.EventListener;
import org.springframework.security.core.userdetails.ReactiveUserDetailsService;
import org.springframework.security.core.userdetails.UserDetailsService;
import org.springframework.security.crypto.password.PasswordEncoder;
import org.springframework.util.Assert;
import org.springframework.util.ClassUtils;

/**
 * The ldaptive user details auto configuration.
 *
 * @author Christian Bremer
 */
@Configuration
@ConditionalOnWebApplication(type = Type.ANY)
@AutoConfigureBefore(InMemoryUserDetailsAutoConfiguration.class)
@AutoConfigureAfter(LdaptiveAutoConfiguration.class)
@ConditionalOnClass({
    ConnectionFactory.class,
    LdaptiveOperations.class
})
@ConditionalOnProperty(
    prefix = "bremersee.ldaptive",
    name = {"enabled", "authentication-enabled"},
    havingValue = "true")
@EnableConfigurationProperties(LdaptiveProperties.class)
@Slf4j
public class LdaptiveUserDetailsAutoConfiguration {

  private final UserDetailsProperties properties;

  private final LdaptiveOperations ldaptiveOperations;

  /**
   * Instantiates a new ldaptive user details auto configuration.
   *
   * @param properties the properties
   * @param ldaptiveOperationsProvider the ldaptive operations provider
   */
  public LdaptiveUserDetailsAutoConfiguration(
      LdaptiveProperties properties,
      ObjectProvider<LdaptiveOperations> ldaptiveOperationsProvider) {
    this.properties = properties.getUserDetails();
    this.ldaptiveOperations = ldaptiveOperationsProvider.getIfAvailable();
    Assert.notNull(this.ldaptiveOperations, "Ldap operations must not be present.");
  }

  /**
   * Init.
   */
  @EventListener(ApplicationReadyEvent.class)
  public void init() {
    log.info("\n"
            + "*********************************************************************************\n"
            + "* {}\n"
            + "*********************************************************************************\n"
            + "* properties = {}\n"
            + "*********************************************************************************",
        ClassUtils.getUserClass(getClass()).getSimpleName(),
        properties);
    Assert.hasText(properties.getUserBaseDn(), "User base dn must be present.");
    Assert.hasText(properties.getUserFindOneFilter(), "User find one filter must be present.");
  }

  /**
   * Ldaptive user details service.
   *
   * @return the ldaptive user details service
   */
  @ConditionalOnWebApplication(type = Type.SERVLET)
  @ConditionalOnMissingBean(value = {UserDetailsService.class})
  @Bean
  public LdaptiveUserDetailsService ldaptiveUserDetailsService() {
    return new LdaptiveUserDetailsService(
        ldaptiveOperations,
        properties.getUserBaseDn(),
        properties.getUserFindOneFilter(),
        properties.getUserFindOneSearchScope(),
        properties.getUserAccountControlAttributeName(),
        properties.getAuthorities(),
        properties.getAuthorityAttributeName(),
        properties.isAuthorityDn(),
        properties.getAuthorityMap(),
        properties.getAuthorityPrefix());
  }

  /**
   * Reactive ldaptive user details service.
   *
   * @param reactiveLdaptiveOperationsProvider the reactive ldaptive operations provider
   * @return the reactive ldaptive user details service
   */
  @ConditionalOnWebApplication(type = Type.REACTIVE)
  @ConditionalOnMissingBean(value = {ReactiveUserDetailsService.class})
  @Bean
  public ReactiveLdaptiveUserDetailsService reactiveLdaptiveUserDetailsService(
      ObjectProvider<ReactiveLdaptiveOperations> reactiveLdaptiveOperationsProvider) {
    ReactiveLdaptiveOperations reactiveLdaptiveOperations = reactiveLdaptiveOperationsProvider.getIfAvailable();
    Assert.notNull(reactiveLdaptiveOperations, "Reactive ldap operations must not be present.");
    return new ReactiveLdaptiveUserDetailsService(
        reactiveLdaptiveOperations,
        properties.getUserBaseDn(),
        properties.getUserFindOneFilter(),
        properties.getUserFindOneSearchScope(),
        properties.getUserAccountControlAttributeName(),
        properties.getAuthorities(),
        properties.getAuthorityAttributeName(),
        properties.isAuthorityDn(),
        properties.getAuthorityMap(),
        properties.getAuthorityPrefix());
  }

  /**
   * Ldaptive password matcher.
   *
   * @return the ldaptive password matcher
   */
  @ConditionalOnMissingBean(value = {PasswordEncoder.class})
  @Bean
  public LdaptivePasswordMatcher passwordEncoder() {
    LdaptivePasswordMatcher matcher = new LdaptivePasswordMatcher(
        ldaptiveOperations,
        properties.getUserBaseDn(),
        properties.getUserFindOneFilter());
    matcher.setUserPasswordAttributeName(properties.getUserAccountControlAttributeName());
    matcher.setUserFindOneSearchScope(properties.getUserFindOneSearchScope());
    matcher.setUserPasswordAttributeName(properties.getUserPasswordAttributeName());
    matcher.setDelegate(new LdaptivePasswordEncoder(
        properties.getUserPasswordLabel(),
        properties.getUserPasswordAlgorithm()));
    return matcher;
  }

}