diff --git a/brick/src/main/java/ch/cyberduck/core/brick/BrickUnauthorizedRetryStrategy.java b/brick/src/main/java/ch/cyberduck/core/brick/BrickUnauthorizedRetryStrategy.java index 85cd93875c1..1a16271e293 100644 --- a/brick/src/main/java/ch/cyberduck/core/brick/BrickUnauthorizedRetryStrategy.java +++ b/brick/src/main/java/ch/cyberduck/core/brick/BrickUnauthorizedRetryStrategy.java @@ -39,12 +39,12 @@ import org.apache.logging.log4j.Logger; import java.io.IOException; -import java.util.concurrent.Semaphore; +import java.util.concurrent.locks.ReentrantLock; public class BrickUnauthorizedRetryStrategy extends DisabledServiceUnavailableRetryStrategy implements HttpRequestInterceptor { private static final Logger log = LogManager.getLogger(BrickUnauthorizedRetryStrategy.class); - private final Semaphore semaphore = new Semaphore(1); + private final ReentrantLock lock = new ReentrantLock(); private final HostPasswordStore store = PasswordStoreFactory.get(); private final BrickSession session; private final LoginCallback prompt; @@ -64,7 +64,7 @@ public boolean retryRequest(final HttpResponse response, final int executionCoun case HttpStatus.SC_UNAUTHORIZED: log.debug("Try to acquire semaphore for {}", session); // Pairing token no longer valid - if(!semaphore.tryAcquire()) { + if(!lock.tryLock()) { log.warn("Skip pairing because semaphore cannot be aquired for {}", session); return false; } @@ -84,7 +84,7 @@ public boolean retryRequest(final HttpResponse response, final int executionCoun } finally { log.debug("Release semaphore for {}", session); - semaphore.release(); + lock.unlock(); } } return false; diff --git a/ctera/src/main/java/ch/cyberduck/core/ctera/CteraAuthenticationHandler.java b/ctera/src/main/java/ch/cyberduck/core/ctera/CteraAuthenticationHandler.java index bf073cd60aa..6fecf9f6955 100644 --- a/ctera/src/main/java/ch/cyberduck/core/ctera/CteraAuthenticationHandler.java +++ b/ctera/src/main/java/ch/cyberduck/core/ctera/CteraAuthenticationHandler.java @@ -71,6 +71,7 @@ import java.util.Optional; import java.util.concurrent.CountDownLatch; import java.util.concurrent.atomic.AtomicReference; +import java.util.concurrent.locks.ReentrantLock; import com.fasterxml.jackson.core.JsonProcessingException; import com.fasterxml.jackson.databind.ObjectMapper; @@ -84,6 +85,7 @@ public class CteraAuthenticationHandler implements ServiceUnavailableRetryStrate public static final String ATTACH_DEVICE_ACTIVATION_CODE_PATH = "/ServicesPortal/public/users?format=jsonext"; public static final String ATTACH_DEVICE_USERNAME_PATH = "/ServicesPortal/public/users/%s?format=jsonext"; + private final ReentrantLock lock = new ReentrantLock(); private final CteraSession session; private final Host host; private final LoginCallback prompt; @@ -117,23 +119,29 @@ public CteraAuthenticationHandler withInfo(final PublicInfo info) { } public CteraTokens validate() throws BackgroundException { - if(tokens.validate()) { - log.debug("Authorize with saved tokens {}", tokens); - } - else { - tokens = this.attach(); - } - log.debug("Authorize with tokens {}", tokens); + lock.lock(); try { - this.authorize(); + if(tokens.validate()) { + log.debug("Authorize with saved tokens {}", tokens); + } + else { + tokens = this.attach(); + } + log.debug("Authorize with tokens {}", tokens); + try { + this.authorize(); + } + catch(AccessDeniedException e) { + // Try to re-authenticate with new tokens + log.warn("Failure {} authorizing with tokens {}", e, tokens); + tokens = this.attach(); + this.authorize(); + } + return tokens; } - catch(AccessDeniedException e) { - // Try to re-authenticate with new tokens - log.warn("Failure {} authorizing with tokens {}", e, tokens); - tokens = this.attach(); - this.authorize(); + finally { + lock.unlock(); } - return tokens; } /** @@ -162,37 +170,43 @@ private CteraTokens attach() throws BackgroundException { } public void authorize() throws BackgroundException { - final HttpPost login = new HttpPost(AUTH_PATH); + lock.lock(); try { - login.setEntity( - new StringEntity(String.format("j_username=device%%5c%s&j_password=%s", - tokens.getDeviceId(), tokens.getSharedSecret()), ContentType.APPLICATION_FORM_URLENCODED - ) - ); - session.getClient().execute(login, new AbstractResponseHandler() { - @Override - public Void handleResponse(final HttpResponse response) throws IOException { - if(!response.containsHeader("Set-Cookie")) { - log.warn("No cookie in response {}", response); + final HttpPost login = new HttpPost(AUTH_PATH); + try { + login.setEntity( + new StringEntity(String.format("j_username=device%%5c%s&j_password=%s", + tokens.getDeviceId(), tokens.getSharedSecret()), ContentType.APPLICATION_FORM_URLENCODED + ) + ); + session.getClient().execute(login, new AbstractResponseHandler() { + @Override + public Void handleResponse(final HttpResponse response) throws IOException { + if(!response.containsHeader("Set-Cookie")) { + log.warn("No cookie in response {}", response); + } + else { + final Header header = response.getFirstHeader("Set-Cookie"); + log.debug("Received cookie {}", header); + } + return super.handleResponse(response); } - else { - final Header header = response.getFirstHeader("Set-Cookie"); - log.debug("Received cookie {}", header); - } - return super.handleResponse(response); - } - @Override - public Void handleEntity(final HttpEntity entity) { - return null; - } - }); - } - catch(HttpResponseException e) { - throw new DefaultHttpResponseExceptionMappingService().map(e); + @Override + public Void handleEntity(final HttpEntity entity) { + return null; + } + }); + } + catch(HttpResponseException e) { + throw new DefaultHttpResponseExceptionMappingService().map(e); + } + catch(IOException e) { + throw new HttpExceptionMappingService().map(e); + } } - catch(IOException e) { - throw new HttpExceptionMappingService().map(e); + finally { + lock.unlock(); } } diff --git a/oauth/src/main/java/ch/cyberduck/core/oauth/OAuth2RequestInterceptor.java b/oauth/src/main/java/ch/cyberduck/core/oauth/OAuth2RequestInterceptor.java index 1498b7017a4..2573f60a933 100644 --- a/oauth/src/main/java/ch/cyberduck/core/oauth/OAuth2RequestInterceptor.java +++ b/oauth/src/main/java/ch/cyberduck/core/oauth/OAuth2RequestInterceptor.java @@ -38,12 +38,15 @@ import java.io.IOException; import java.util.List; +import java.util.concurrent.locks.ReentrantLock; import com.google.api.client.auth.oauth2.Credential; public class OAuth2RequestInterceptor extends OAuth2AuthorizationService implements HttpRequestInterceptor { private static final Logger log = LogManager.getLogger(OAuth2RequestInterceptor.class); + private final ReentrantLock lock = new ReentrantLock(); + /** * Currently valid tokens */ @@ -75,22 +78,34 @@ public Credentials validate() throws BackgroundException { @Override public OAuthTokens authorize() throws BackgroundException { - return tokens = super.authorize(); + lock.lock(); + try { + return tokens = super.authorize(); + } + finally { + lock.unlock(); + } } /** * Refresh with cached refresh token */ public OAuthTokens refresh() throws BackgroundException { - return tokens = this.refresh(tokens); + lock.lock(); + try { + return tokens = this.refresh(tokens); + } + finally { + lock.unlock(); + } } /** - * * @param previous Refresh token */ @Override public OAuthTokens refresh(final OAuthTokens previous) throws BackgroundException { + lock.lock(); try { return tokens = super.refresh(previous); } @@ -98,28 +113,37 @@ public OAuthTokens refresh(final OAuthTokens previous) throws BackgroundExceptio log.warn("Failure {} refreshing OAuth tokens", e.getMessage()); return tokens = this.authorize(); } + finally { + lock.unlock(); + } } @Override public void process(final HttpRequest request, final HttpContext context) throws HttpException, IOException { - if(tokens.isExpired()) { - try { - final OAuthTokens previous = tokens; - final OAuthTokens refreshed = this.refresh(tokens); - // Skip saving tokens when not changed - if(!refreshed.equals(previous)) { - this.save(refreshed); + lock.lock(); + try { + if(tokens.isExpired()) { + try { + final OAuthTokens previous = tokens; + final OAuthTokens refreshed = this.refresh(tokens); + // Skip saving tokens when not changed + if(!refreshed.equals(previous)) { + this.save(refreshed); + } + } + catch(BackgroundException e) { + log.warn("Failure {} refreshing OAuth tokens {}", e, tokens); + // Follow-up error 401 handled in error interceptor } } - catch(BackgroundException e) { - log.warn("Failure {} refreshing OAuth tokens {}", e, tokens); - // Follow-up error 401 handled in error interceptor + if(StringUtils.isNotBlank(tokens.getAccessToken())) { + log.info("Authorizing service request with OAuth2 tokens {}", tokens); + request.removeHeaders(HttpHeaders.AUTHORIZATION); + request.addHeader(new BasicHeader(HttpHeaders.AUTHORIZATION, String.format("Bearer %s", tokens.getAccessToken()))); } } - if(StringUtils.isNotBlank(tokens.getAccessToken())) { - log.info("Authorizing service request with OAuth2 tokens {}", tokens); - request.removeHeaders(HttpHeaders.AUTHORIZATION); - request.addHeader(new BasicHeader(HttpHeaders.AUTHORIZATION, String.format("Bearer %s", tokens.getAccessToken()))); + finally { + lock.unlock(); } } @@ -148,6 +172,12 @@ public OAuth2RequestInterceptor withParameter(final String key, final String val } public OAuthTokens getTokens() { - return tokens; + lock.lock(); + try { + return tokens; + } + finally { + lock.unlock(); + } } } diff --git a/s3/src/main/java/ch/cyberduck/core/sts/STSAssumeRoleCredentialsRequestInterceptor.java b/s3/src/main/java/ch/cyberduck/core/sts/STSAssumeRoleCredentialsRequestInterceptor.java index d0528d87266..b810706de12 100644 --- a/s3/src/main/java/ch/cyberduck/core/sts/STSAssumeRoleCredentialsRequestInterceptor.java +++ b/s3/src/main/java/ch/cyberduck/core/sts/STSAssumeRoleCredentialsRequestInterceptor.java @@ -16,10 +16,8 @@ */ import ch.cyberduck.core.Credentials; -import ch.cyberduck.core.HostPasswordStore; import ch.cyberduck.core.LoginCallback; import ch.cyberduck.core.OAuthTokens; -import ch.cyberduck.core.PasswordStoreFactory; import ch.cyberduck.core.TemporaryAccessTokens; import ch.cyberduck.core.exception.BackgroundException; import ch.cyberduck.core.exception.LoginFailureException; @@ -38,6 +36,7 @@ import org.jets3t.service.security.AWSSessionCredentials; import java.io.IOException; +import java.util.concurrent.locks.ReentrantLock; import com.amazonaws.services.securitytoken.AWSSecurityTokenService; import com.auth0.jwt.JWT; @@ -49,12 +48,13 @@ public class STSAssumeRoleCredentialsRequestInterceptor extends STSAssumeRoleAuthorizationService implements S3CredentialsStrategy, HttpRequestInterceptor { private static final Logger log = LogManager.getLogger(STSAssumeRoleCredentialsRequestInterceptor.class); + private final ReentrantLock lock = new ReentrantLock(); + /** * Currently valid tokens */ private TemporaryAccessTokens tokens = TemporaryAccessTokens.EMPTY; - private final HostPasswordStore store = PasswordStoreFactory.get(); /** * Handle authentication with OpenID connect retrieving token for STS */ @@ -77,6 +77,7 @@ public STSAssumeRoleCredentialsRequestInterceptor(final OAuth2RequestInterceptor } public TemporaryAccessTokens refresh(final OAuthTokens oidc) throws BackgroundException { + lock.lock(); try { return this.tokens = this.authorize(oidc); } @@ -85,22 +86,31 @@ public TemporaryAccessTokens refresh(final OAuthTokens oidc) throws BackgroundEx log.warn("Failure {} authorizing. Retry with refreshed OAuth tokens", e.getMessage()); return this.tokens = this.authorize(oauth.refresh(oidc)); } + finally { + lock.unlock(); + } } @Override public void process(final HttpRequest request, final HttpContext context) throws HttpException, IOException { - if(tokens.isExpired()) { - try { - this.refresh(oauth.getTokens()); - log.info("Authorizing service request with STS tokens {}", tokens); - session.getClient().setProviderCredentials(new AWSSessionCredentials(tokens.getAccessKeyId(), tokens.getSecretAccessKey(), - tokens.getSessionToken())); - } - catch(BackgroundException e) { - log.warn("Failure {} refreshing STS tokens {}", e, tokens); - // Follow-up error 401 handled in error interceptor + lock.lock(); + try { + if(tokens.isExpired()) { + try { + this.refresh(oauth.getTokens()); + log.info("Authorizing service request with STS tokens {}", tokens); + session.getClient().setProviderCredentials(new AWSSessionCredentials(tokens.getAccessKeyId(), tokens.getSecretAccessKey(), + tokens.getSessionToken())); + } + catch(BackgroundException e) { + log.warn("Failure {} refreshing STS tokens {}", e, tokens); + // Follow-up error 401 handled in error interceptor + } } } + finally { + lock.unlock(); + } } @Override