use of com.gw2auth.oauth2.server.service.gw2.Gw2SubToken in project oauth2-server by gw2auth.
the class OAuth2TokenCustomizerService method customize.
private void customize(JwtEncodingContext ctx, String clientAuthorizationId, long accountId, long clientRegistrationId) {
final ClientAuthorization clientAuthorization = this.clientAuthorizationService.getClientAuthorization(accountId, clientAuthorizationId).orElse(null);
final ClientConsent clientConsent = this.clientConsentService.getClientConsent(accountId, clientRegistrationId).orElse(null);
if (clientAuthorization == null || clientConsent == null) {
throw new OAuth2AuthenticationException(new OAuth2Error(OAuth2ErrorCodes.ACCESS_DENIED));
}
try (ClientConsentService.LoggingContext logging = this.clientConsentService.log(accountId, clientRegistrationId, ClientConsentService.LogType.ACCESS_TOKEN)) {
final Set<String> effectiveAuthorizedScopes = new HashSet<>(clientConsent.authorizedScopes());
effectiveAuthorizedScopes.retainAll(clientAuthorization.authorizedScopes());
final Set<UUID> authorizedGw2AccountIds = clientAuthorization.gw2AccountIds();
final Set<Gw2ApiPermission> authorizedGw2ApiPermissions = effectiveAuthorizedScopes.stream().flatMap((scope) -> Gw2ApiPermission.fromOAuth2(scope).stream()).collect(Collectors.toSet());
if (authorizedGw2ApiPermissions.isEmpty() || authorizedGw2AccountIds.isEmpty()) {
logging.log("The Consent has been removed: responding with ACCESS_DENIED");
throw new OAuth2AuthenticationException(new OAuth2Error(OAuth2ErrorCodes.ACCESS_DENIED));
}
final List<ApiToken> authorizedRootTokens = this.apiTokenService.getApiTokens(accountId, authorizedGw2AccountIds);
// in theory, this should not happen since authorized-tokens and root-tokens are related via foreign key
if (authorizedRootTokens.isEmpty()) {
logging.log("All linked Root-API-Tokens have been removed: responding with ACCESS_DENIED");
throw new OAuth2AuthenticationException(new OAuth2Error(OAuth2ErrorCodes.ACCESS_DENIED));
}
final Set<UUID> verifiedGw2AccountIds;
final boolean hasGw2AuthVerifiedScope = effectiveAuthorizedScopes.contains(ClientConsentService.GW2AUTH_VERIFIED_SCOPE);
if (hasGw2AuthVerifiedScope) {
verifiedGw2AccountIds = this.verificationService.getVerifiedGw2AccountIds(accountId);
} else {
verifiedGw2AccountIds = Set.of();
}
final int gw2ApiPermissionsBitSet = Gw2ApiPermission.toBitSet(authorizedGw2ApiPermissions);
final List<ApiSubTokenEntity> savedSubTokens = this.apiSubTokenRepository.findAllByAccountIdGw2AccountIdsAndGw2ApiPermissionsBitSet(accountId, authorizedGw2AccountIds, gw2ApiPermissionsBitSet);
final Instant atLeastValidUntil = this.clock.instant().plus(AUTHORIZED_TOKEN_MIN_EXCESS_TIME);
final Map<UUID, ApiSubTokenEntity> savedSubTokenByGw2AccountId = new HashMap<>(savedSubTokens.size());
final Map<Instant, Integer> savedSubTokenCountByExpirationTime = new HashMap<>(savedSubTokens.size());
Instant expirationTimeWithMostSavedSubTokens = null;
for (ApiSubTokenEntity savedSubToken : savedSubTokens) {
if (savedSubToken.expirationTime().isAfter(atLeastValidUntil)) {
savedSubTokenByGw2AccountId.put(savedSubToken.gw2AccountId(), savedSubToken);
final int groupCount = savedSubTokenCountByExpirationTime.merge(savedSubToken.expirationTime(), 1, Integer::sum);
if (expirationTimeWithMostSavedSubTokens == null || groupCount > savedSubTokenCountByExpirationTime.get(expirationTimeWithMostSavedSubTokens)) {
expirationTimeWithMostSavedSubTokens = savedSubToken.expirationTime();
}
}
}
final Instant expirationTime;
if (expirationTimeWithMostSavedSubTokens != null) {
// if existing subtokens which are still valid for at least AUTHORIZED_TOKEN_MIN_EXCESS_TIME could be found, use this expiration time
ctx.getClaims().expiresAt(expirationTimeWithMostSavedSubTokens);
expirationTime = expirationTimeWithMostSavedSubTokens;
} else {
expirationTime = ctx.getClaims().build().getExpiresAt();
}
final Map<UUID, Map<String, Object>> tokensForJWT = new LinkedHashMap<>(authorizedGw2AccountIds.size());
final Batch.Builder<Map<UUID, Pair<ApiToken, Gw2SubToken>>> batch = Batch.builder();
for (ApiToken authorizedRootToken : authorizedRootTokens) {
final Map<String, Object> tokenForJWT = new HashMap<>(3);
final UUID gw2AccountId = authorizedRootToken.gw2AccountId();
final String displayName = authorizedRootToken.displayName();
final ApiSubTokenEntity potentialExistingSubToken = savedSubTokenByGw2AccountId.get(gw2AccountId);
tokenForJWT.put("name", displayName);
if (potentialExistingSubToken != null && potentialExistingSubToken.expirationTime().equals(expirationTime)) {
tokenForJWT.put("token", potentialExistingSubToken.gw2ApiSubtoken());
logging.log("Using existing and valid Subtoken for the Root-API-Token named '%s'", displayName);
} else {
if (authorizedRootToken.gw2ApiPermissions().containsAll(authorizedGw2ApiPermissions)) {
final String gw2ApiToken = authorizedRootToken.gw2ApiToken();
batch.add((timeout) -> this.gw2APIService.withTimeout(timeout, () -> this.gw2APIService.createSubToken(gw2ApiToken, authorizedGw2ApiPermissions, expirationTime)), (accumulator, context) -> {
try {
accumulator.put(gw2AccountId, new Pair<>(authorizedRootToken, context.get()));
} catch (ExecutionException | TimeoutException e) {
accumulator.put(gw2AccountId, new Pair<>(authorizedRootToken, null));
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
accumulator.put(gw2AccountId, new Pair<>(authorizedRootToken, null));
}
return accumulator;
});
} else {
logging.log("The Root-API-Token named '%s' has less permissions than the authorization", displayName);
}
}
if (hasGw2AuthVerifiedScope) {
final boolean isVerified = verifiedGw2AccountIds.contains(gw2AccountId);
tokenForJWT.put("verified", isVerified);
logging.log("Including verified=%s for the Root-API-Token named '%s'", isVerified, displayName);
}
tokensForJWT.put(gw2AccountId, tokenForJWT);
}
final Map<UUID, Pair<ApiToken, Gw2SubToken>> result = batch.build().execute(this.gw2ApiClientExecutorService, HashMap::new, 10L, TimeUnit.SECONDS);
final List<ApiTokenValidityUpdate> apiTokenValidityUpdates = new ArrayList<>(result.size());
final List<ApiSubTokenEntity> apiSubTokenEntitiesToSave = new ArrayList<>(result.size());
for (Map.Entry<UUID, Pair<ApiToken, Gw2SubToken>> entry : result.entrySet()) {
final UUID gw2AccountId = entry.getKey();
final Map<String, Object> tokenForJWT = tokensForJWT.get(gw2AccountId);
final String displayName = entry.getValue().v1().displayName();
final Gw2SubToken gw2SubToken = entry.getValue().v2();
if (gw2SubToken != null) {
if (gw2SubToken.permissions().equals(authorizedGw2ApiPermissions)) {
apiSubTokenEntitiesToSave.add(new ApiSubTokenEntity(accountId, gw2AccountId, gw2ApiPermissionsBitSet, gw2SubToken.value(), expirationTime));
tokenForJWT.put("token", gw2SubToken.value());
logging.log("Added Subtoken for the Root-API-Token named '%s'", displayName);
} else {
tokenForJWT.put("error", "Failed to obtain new subtoken");
logging.log("The retrieved Subtoken for the Root-API-Token named '%s' appears to have less permissions than the authorization", displayName);
}
apiTokenValidityUpdates.add(new ApiTokenValidityUpdate(accountId, gw2AccountId, true));
} else {
tokenForJWT.put("error", "Failed to obtain new subtoken");
logging.log("Failed to retrieve a new Subtoken for the Root-API-Token named '%s' from the GW2-API", displayName);
}
}
this.apiTokenService.updateApiTokensValid(this.clock.instant(), apiTokenValidityUpdates);
this.apiSubTokenRepository.saveAll(apiSubTokenEntitiesToSave);
customize(ctx, clientConsent.accountSub(), authorizedGw2ApiPermissions, tokensForJWT);
}
}
use of com.gw2auth.oauth2.server.service.gw2.Gw2SubToken in project oauth2-server by gw2auth.
the class Gw2ApiServiceImpl method createSubToken.
@Override
public Gw2SubToken createSubToken(String token, Set<Gw2ApiPermission> permissions, Instant expirationTime) {
final MultiValueMap<String, String> query = new LinkedMultiValueMap<>();
query.add("permissions", permissions.stream().map(Gw2ApiPermission::gw2).collect(Collectors.joining(",")));
// ISO-8601
query.add("expire", expirationTime.toString());
final String jwtString = getFromAPI("/v2/createsubtoken", query, token, GW2CreateSubToken.class).subtoken();
final Set<Gw2ApiPermission> gw2ApiPermissions;
try {
final JWT jwt = JWTParser.parse(jwtString);
gw2ApiPermissions = Optional.ofNullable(jwt.getJWTClaimsSet().getStringListClaim("permissions")).stream().flatMap(List::stream).flatMap((permission) -> Gw2ApiPermission.fromGw2(permission).stream()).collect(Collectors.toSet());
} catch (ParseException e) {
throw new Gw2ApiServiceException(Gw2ApiServiceException.SUBTOKEN_JWT_PARSING_ERROR);
}
return new Gw2SubToken(jwtString, gw2ApiPermissions);
}
use of com.gw2auth.oauth2.server.service.gw2.Gw2SubToken in project oauth2-server by gw2auth.
the class VerificationServiceImpl method submitChallenge.
@Override
@Transactional
public VerificationChallengeSubmit submitChallenge(long accountId, String gw2ApiToken) {
Gw2AccountVerificationChallengeEntity entity = this.gw2AccountVerificationChallengeRepository.findByAccountIdAndGw2AccountId(accountId, STARTED_CHALLENGE_GW2_ACCOUNT_ID).orElseThrow(() -> new Gw2AccountVerificationServiceException(Gw2AccountVerificationServiceException.CHALLENGE_NOT_FOUND, HttpStatus.NOT_FOUND));
this.gw2AccountVerificationChallengeRepository.deleteByAccountIdAndGw2AccountId(accountId, STARTED_CHALLENGE_GW2_ACCOUNT_ID);
final VerificationChallenge<?> challenge = this.challengesById.get(entity.challengeId());
if (challenge == null) {
throw new Gw2AccountVerificationServiceException(Gw2AccountVerificationServiceException.CHALLENGE_NOT_FOUND, HttpStatus.NOT_FOUND);
}
final Instant startTime = this.clock.instant();
final Instant timeout = startTime.plus(challenge.getTimeout());
final Gw2SubToken gw2SubToken = this.gw2ApiService.createSubToken(gw2ApiToken, challenge.getRequiredGw2ApiPermissions(), timeout);
if (!gw2SubToken.permissions().containsAll(challenge.getRequiredGw2ApiPermissions())) {
throw new Gw2AccountVerificationServiceException(Gw2AccountVerificationServiceException.INSUFFICIENT_PERMISSIONS, HttpStatus.BAD_REQUEST);
}
final UUID gw2AccountId = this.gw2ApiService.getAccount(gw2SubToken.value()).id();
final Gw2AccountVerificationChallengeEntity pendingChallengeEntity = this.gw2AccountVerificationChallengeRepository.findByAccountIdAndGw2AccountId(accountId, gw2AccountId.toString()).orElse(null);
if (pendingChallengeEntity != null) {
if (pendingChallengeEntity.challengeId() == VERIFICATION_FAILED_CHALLENGE_ID) {
final Duration timeUntilAvailable = Duration.between(this.clock.instant(), pendingChallengeEntity.timeoutAt());
final long minutes = timeUntilAvailable.toMinutes();
// a verification for this gw2-account failed before
throw new Gw2AccountVerificationServiceException(String.format(Gw2AccountVerificationServiceException.CHALLENGE_FOR_THIS_ACCOUNT_BLOCKED, minutes), HttpStatus.BAD_REQUEST);
} else {
// allow only one active challenge per gw2 account
throw new Gw2AccountVerificationServiceException(Gw2AccountVerificationServiceException.CHALLENGE_FOR_THIS_GW2_ACCOUNT_ALREADY_STARTED, HttpStatus.BAD_REQUEST);
}
} else if (this.gw2AccountVerificationRepository.findById(gw2AccountId).map(Gw2AccountVerificationEntity::accountId).orElse(-1L) == accountId) {
// if this gw2 account is already verified for this same gw2auth account, dont proceed
throw new Gw2AccountVerificationServiceException(Gw2AccountVerificationServiceException.GW2_ACCOUNT_ALREADY_VERIFIED, HttpStatus.BAD_REQUEST);
}
entity = new Gw2AccountVerificationChallengeEntity(entity.accountId(), gw2AccountId.toString(), entity.challengeId(), entity.state(), gw2SubToken.value(), startTime, timeout);
final boolean isVerified = verify(entity);
final VerificationChallengePending verificationChallengePending;
if (isVerified) {
verificationChallengePending = null;
} else {
this.gw2AccountVerificationChallengeRepository.save(entity);
verificationChallengePending = new VerificationChallengePending(entity.challengeId(), gw2AccountId, startTime);
}
return new VerificationChallengeSubmit(verificationChallengePending, isVerified);
}
Aggregations