001/*
002 * Licensed under the Apache License, Version 2.0 (the "License");
003 * you may not use this file except in compliance with the License.
004 * You may obtain a copy of the License at
005 *
006 *     http://www.apache.org/licenses/LICENSE-2.0
007 *
008 * Unless required by applicable law or agreed to in writing, software
009 * distributed under the License is distributed on an "AS IS" BASIS,
010 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
011 * See the License for the specific language governing permissions and
012 * limitations under the License.
013 */
014package org.gbif.ws.remoteauth;
015
016import org.gbif.api.vocabulary.UserRole;
017
018import java.util.Collection;
019import java.util.Collections;
020import java.util.Objects;
021import java.util.Optional;
022import java.util.stream.Collectors;
023
024import org.springframework.http.HttpHeaders;
025import org.springframework.http.ResponseEntity;
026import org.springframework.retry.annotation.Backoff;
027import org.springframework.retry.annotation.Retryable;
028import org.springframework.security.authentication.AuthenticationProvider;
029import org.springframework.security.core.Authentication;
030import org.springframework.security.core.AuthenticationException;
031import org.springframework.security.core.authority.SimpleGrantedAuthority;
032
033import com.fasterxml.jackson.databind.DeserializationFeature;
034import com.fasterxml.jackson.databind.ObjectMapper;
035import com.fasterxml.jackson.databind.ObjectReader;
036
037import lombok.Data;
038import lombok.SneakyThrows;
039import lombok.extern.slf4j.Slf4j;
040
041/**
042 * Base class for authentication against remote end-points.
043 *
044 * @param <T> supported authentication type.
045 */
046@Slf4j
047@Data
048public abstract class AbstractRemoteAuthenticationProvider<T extends Authentication>
049    implements AuthenticationProvider {
050
051  protected static final ObjectReader OBJECT_READER =
052      new ObjectMapper().disable(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES).reader();
053
054  private final RemoteAuthClient remoteAuthClient;
055
056  private final Class<T> authClass;
057
058  private final String authWsPath;
059
060  public AbstractRemoteAuthenticationProvider(
061      Class<T> authClass, String authWsPath, RemoteAuthClient remoteAuthClient) {
062    this.authClass = authClass;
063    this.authWsPath = authWsPath;
064    this.remoteAuthClient = remoteAuthClient;
065  }
066
067  @Override
068  public Authentication authenticate(Authentication authentication) throws AuthenticationException {
069    return createSuccessAuthentication(tryLogin((T) authentication), authentication);
070  }
071
072  @Override
073  public boolean supports(Class<?> authentication) {
074    return authClass.isAssignableFrom(authentication);
075  }
076
077  /** Performs the remote call to the login service. */
078  @Retryable(value = RuntimeException.class, maxAttempts = 5, backoff = @Backoff(delay = 300))
079  protected ResponseEntity<String> tryLogin(T authentication) {
080    return remoteAuthClient.remoteAuth(authWsPath, createHttpHeaders(authentication));
081  }
082
083  public abstract HttpHeaders createHttpHeaders(Authentication authentication);
084
085  /** Creates an UsernamePasswordAuthenticationToken from the supplied parameters. */
086  protected abstract Authentication createSuccessAuthentication(
087      ResponseEntity<String> response, Authentication authentication);
088
089  /** Maps User roles to a list SimpleGrantedAuthority. */
090  protected Collection<SimpleGrantedAuthority> extractRoles(LoggedUser loggedUser) {
091    Objects.requireNonNull(loggedUser);
092    return Optional.ofNullable(loggedUser.getRoles())
093        .map(
094            roles ->
095                roles.stream()
096                    .map(r -> new SimpleGrantedAuthority(UserRole.valueOf(r).name()))
097                    .collect(Collectors.toList()))
098        .orElse(Collections.emptyList());
099  }
100
101  @SneakyThrows
102  protected LoggedUser readUserFromResponse(ResponseEntity<String> response) {
103    return OBJECT_READER.readValue(response.getBody(), LoggedUser.class);
104  }
105}