Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Synchronize access to in memory tokens #16556

Merged
merged 5 commits into from
Nov 20, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
}
Expand All @@ -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;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand Down Expand Up @@ -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;
}

/**
Expand Down Expand Up @@ -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<Void>() {
@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<Void>() {
@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();
}
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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
*/
Expand Down Expand Up @@ -75,51 +78,72 @@ 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);
}
catch(LoginFailureException e) {
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();
}
}

Expand Down Expand Up @@ -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();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -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;
Expand All @@ -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
*/
Expand All @@ -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);
}
Expand All @@ -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
Expand Down