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}