Skip to content

Commit

Permalink
Merge branch 'rc-v0.4.31' into S154-secrets_updated_too_much
Browse files Browse the repository at this point in the history
  • Loading branch information
eschultink authored Jul 26, 2023
2 parents e97257b + 9613277 commit 0a88e6d
Show file tree
Hide file tree
Showing 3 changed files with 152 additions and 16 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -198,7 +198,28 @@ public static class TokenRefreshHandlerImpl implements OAuth2CredentialsWithRefr
// open a lambda waiting for a lock
private static final String TOKEN_REFRESH_LOCK_ID = "oauth_refresh_token";
private static final int MAX_TOKEN_REFRESH_ATTEMPTS = 3;
private static final long WAIT_AFTER_FAILED_LOCK_ATTEMPTS = 2000L;


/**
* how long to wait after a failed lock attempt before trying again; multiplier on the
* attempt.
*
* goal is that this value should be big enough to allow the process that holds the lock to
* 1) refresh the token it
* 2) write it
* 3) release the lock
* 4) have that write be visible to other processes, given eventual consistency in GCP
* Secret Manager case
*
* this includes the allowance for eventual consistency, so wait should be >= that value
* in practice.
*/
private static final long WAIT_AFTER_FAILED_LOCK_ATTEMPTS = 4000L;

/**
* how long to allow for eventual consistency after write to config
*/
private static final long ALLOWANCE_FOR_EVENTUAL_CONSISTENCY_SECONDS = 2L;

/**
* implements canonical oauth flow to exchange refreshToken for accessToken
Expand All @@ -222,29 +243,29 @@ private AccessToken refreshAccessToken(int attempt) throws IOException {
AccessToken token = getSharedAccessTokenIfSupported().orElse(null);


if (token == null || shouldRefresh(token, clock.instant())) {
if (shouldRefresh(token, clock.instant())) {


// only lock if we're using a shared token across processes
boolean lockNeeded = payloadBuilder.useSharedToken();

boolean acquired = !lockNeeded || lockService.acquire(TOKEN_REFRESH_LOCK_ID, Duration.ofMinutes(2));

if (!acquired) {
//NOTE: check shared access token again, in case the instance which held the
// lock refreshed the token

Uninterruptibles.sleepUninterruptibly(attempt * WAIT_AFTER_FAILED_LOCK_ATTEMPTS, TimeUnit.MILLISECONDS);
refreshAccessToken(attempt + 1);
}

tokenResponse = exchangeRefreshTokenForAccessToken();
token = asAccessToken(tokenResponse);
if (acquired) {
tokenResponse = exchangeRefreshTokenForAccessToken();
token = asAccessToken(tokenResponse);

storeSharedAccessTokenIfSupported(token);
storeSharedAccessTokenIfSupported(token);

if (lockNeeded) {
//q: hold lock extra long, to wait for consistency??
lockService.release(TOKEN_REFRESH_LOCK_ID);
if (lockNeeded) {
// hold lock extra, to try to maximize the time between token refreshes
Uninterruptibles.sleepUninterruptibly(ALLOWANCE_FOR_EVENTUAL_CONSISTENCY_SECONDS, TimeUnit.SECONDS);
lockService.release(TOKEN_REFRESH_LOCK_ID);
}
} else {
//re-try recursively, w/ linear backoff
Uninterruptibles.sleepUninterruptibly(attempt * WAIT_AFTER_FAILED_LOCK_ATTEMPTS, TimeUnit.MILLISECONDS);
token = refreshAccessToken(attempt + 1);
}
}

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
package com.avaulta.gateway.pseudonyms.impl;

import com.avaulta.gateway.pseudonyms.Pseudonym;
import com.avaulta.gateway.pseudonyms.PseudonymEncoder;
import org.apache.commons.lang3.StringUtils;

import java.nio.charset.StandardCharsets;
import java.util.Base64;

/**
* implementation of defacto encoding used by BulkDataSanitizerImpl as of v0.4.30
*/
public class Sha256PseudonymEncoder implements PseudonymEncoder {


@Override
public String encode(Pseudonym pseudonym) {
return base64Encode(pseudonym.getHash());
}

@Override
public Pseudonym decode(String input) {
if (!canBeDecoded(input)) {
throw new IllegalArgumentException("input cannot be decoded");
}

return Pseudonym.builder().hash(base64decode(input)).build();
}

@Override
public boolean canBeDecoded(String possiblePseudonym) {
return possiblePseudonym != null &&
possiblePseudonym.getBytes(StandardCharsets.UTF_8).length == 43; //43 rather than 32, bc of base64 encoding without padding
}

//base64 encoding, to match implementation in HashUtils.java from psoxy-core v0.4.30
String base64Encode(byte[] bytes) {
String encoded = new String(
Base64.getEncoder()
.withoutPadding()
.encode(bytes),
StandardCharsets.UTF_8);
return StringUtils.replaceChars(encoded, "/+", "_.");
}

byte[] base64decode(String input) {
return Base64.getDecoder()
.decode(StringUtils.replaceChars(input, "_.", "/+"));
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
package com.avaulta.gateway.pseudonyms.impl;

import com.avaulta.gateway.pseudonyms.Pseudonym;
import com.avaulta.gateway.pseudonyms.PseudonymEncoder;
import com.avaulta.gateway.tokens.impl.Sha256DeterministicTokenizationStrategy;
import org.apache.commons.lang3.RandomStringUtils;
import org.junit.jupiter.api.Test;
import org.junit.jupiter.params.ParameterizedTest;
import org.junit.jupiter.params.provider.ValueSource;

import java.util.Random;
import java.util.function.Function;

import static org.junit.jupiter.api.Assertions.*;

class Sha256PseudonymEncoderTest {

Sha256PseudonymEncoder encoder = new Sha256PseudonymEncoder();


@ParameterizedTest
@ValueSource(strings = {
// examples taken from https://github.com/Worklytics/psoxy/blob/b483e3788d5457398d55cad7934de959b74c7900/java/core/src/test/java/co/worklytics/psoxy/storage/impl/BulkDataSanitizerImplTest.java#L228-L239
"SappwO4KZKGprqqUNruNreBD2BVR98nEM6NRCu3R2dM",
"mfsaNYuCX__xvnRz4gJp_t0zrDTC5DkuCJvMkubugsI",
".ZdDGUuOMK.Oy7_PJ3pf9SYX12.3tKPdLHfYbjVGcGk",
".fs1T64Micz8SkbILrABgEv4kSg.tFhvhP35HGSLdOo"
})
void canBeDecoded(String encoded) {
assertTrue(encoder.canBeDecoded(encoded));
}


@ParameterizedTest
@ValueSource(strings = {
"asdfasdf",
"1343287afdaskdljf4324sasdfa",
})
void cannotBeDecoded(String encoded) {
assertFalse(encoder.canBeDecoded(encoded));
}

@ParameterizedTest
@ValueSource(strings = {
"asdfasdf",
"1343287afdaskdljf4324sasdfa",
"asdf1234234",
"alice@acme.com"
})
void roundtrip(String identifier) {
Sha256DeterministicTokenizationStrategy sha256DeterministicTokenizationStrategy =
new Sha256DeterministicTokenizationStrategy("salt");

Pseudonym pseudonym = Pseudonym.builder()
.hash(sha256DeterministicTokenizationStrategy.getToken(identifier, Function.identity()))
.build();

String encoded = encoder.encode(pseudonym);

assertTrue(encoder.canBeDecoded(encoded));
assertEquals(new String(pseudonym.getHash()),
new String(encoder.decode(encoded).getHash()));

}
}

0 comments on commit 0a88e6d

Please sign in to comment.