NormalizedGrantedAuthoritiesMapper.java

/*
 * Copyright 2024 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.spring.security.core.authority.mapping;

import static java.util.Objects.nonNull;
import static java.util.Objects.requireNonNullElse;
import static org.springframework.util.ObjectUtils.isEmpty;

import java.util.Collection;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Objects;
import java.util.Optional;
import java.util.stream.Collectors;
import java.util.stream.Stream;
import org.springframework.security.core.GrantedAuthority;
import org.springframework.security.core.authority.SimpleGrantedAuthority;
import org.springframework.security.core.authority.mapping.GrantedAuthoritiesMapper;
import org.springframework.util.Assert;

/**
 * The normalized granted authorities mapper.
 *
 * @author Christian Bremer
 */
public class NormalizedGrantedAuthoritiesMapper implements GrantedAuthoritiesMapper {

  /**
   * The default roles.
   */
  private final List<String> defaultRoles;

  /**
   * The role mapping.
   */
  private final Map<String, String> roleMapping;

  /**
   * The role prefix.
   */
  private final String rolePrefix;

  /**
   * The role case transformation.
   */
  private final CaseTransformation roleCaseTransformation;

  /**
   * The role string replacements.
   */
  private final Map<String, String> roleStringReplacements;

  /**
   * Instantiates a new normalized granted authorities mapper.
   *
   * @param defaultRoles the default roles
   * @param roleMapping the role mapping
   * @param rolePrefix the role prefix
   * @param roleCaseTransformation the role case transformation
   * @param roleStringReplacements the role string replacements
   */
  public NormalizedGrantedAuthoritiesMapper(
      List<String> defaultRoles,
      Map<String, String> roleMapping,
      String rolePrefix,
      CaseTransformation roleCaseTransformation,
      Map<String, String> roleStringReplacements) {
    this.defaultRoles = defaultRoles;
    this.roleMapping = roleMapping;
    this.rolePrefix = requireNonNullElse(rolePrefix, "ROLE_");
    this.roleCaseTransformation = roleCaseTransformation;
    this.roleStringReplacements = roleStringReplacements;
  }

  @Override
  public Collection<? extends GrantedAuthority> mapAuthorities(
      Collection<? extends GrantedAuthority> authorities) {
    Stream<GrantedAuthority> defaultAuthorities = defaultRoles.stream()
        .filter(authority -> !isEmpty(authority))
        .map(SimpleGrantedAuthority::new);
    Stream<GrantedAuthority> givenAuthorities = Stream.ofNullable(authorities)
        .flatMap(Collection::stream)
        .filter(Objects::nonNull)
        .map(GrantedAuthority::getAuthority)
        .filter(authority -> !isEmpty(authority))
        .map(this::normalize);
    return Stream.concat(defaultAuthorities, givenAuthorities)
        .collect(Collectors.toSet());
  }

  /**
   * Normalize granted authority.
   *
   * @param authority the authority
   * @return the granted authority
   */
  protected GrantedAuthority normalize(String authority) {
    Assert.hasText(authority, "Authority must be present.");
    Optional<GrantedAuthority> mappedValue = Stream.ofNullable(roleMapping)
        .flatMap(m -> m.entrySet().stream())
        .filter(e -> authority.equalsIgnoreCase(e.getKey()))
        .findFirst()
        .map(Entry::getValue)
        .map(SimpleGrantedAuthority::new);
    if (mappedValue.isPresent()) {
      return mappedValue.get();
    }
    String value = authority;
    if (nonNull(roleCaseTransformation)) {
      value = switch (roleCaseTransformation) {
        case TO_LOWER_CASE -> value.toLowerCase();
        case TO_UPPER_CASE -> value.toUpperCase();
        case NONE -> value;
      };
    }
    if (!isEmpty(roleStringReplacements)) {
      for (Map.Entry<String, String> replacement : roleStringReplacements.entrySet()) {
        value = value.replaceAll(replacement.getKey(), replacement.getValue());
      }
    }
    if (!isEmpty(rolePrefix) && !value.startsWith(rolePrefix)) {
      value = rolePrefix + value;
    }
    return new SimpleGrantedAuthority(value);
  }

}