use of com.gw2auth.oauth2.server.service.Gw2ApiPermission 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.Gw2ApiPermission in project oauth2-server by gw2auth.
the class ApiTokenServiceImpl method updateApiToken.
@Override
@Transactional(noRollbackFor = ApiTokenOwnershipMismatchException.class)
public ApiToken updateApiToken(long accountId, UUID gw2AccountId, String gw2ApiToken, String displayName) {
final OptionalLong optional = this.verificationService.getVerifiedAccountId(gw2AccountId);
if (optional.isPresent() && optional.getAsLong() != accountId) {
this.apiTokenRepository.deleteByAccountIdAndGw2AccountId(accountId, gw2AccountId);
throw new ApiTokenOwnershipMismatchException();
}
ApiTokenEntity apiTokenEntity = this.apiTokenRepository.findByAccountIdAndGw2AccountId(accountId, gw2AccountId).orElseThrow(() -> new ApiTokenServiceException(ApiTokenServiceException.API_TOKEN_NOT_FOUND, HttpStatus.NOT_FOUND));
if (gw2ApiToken != null) {
final Gw2TokenInfo gw2TokenInfo = this.gw2ApiService.getTokenInfo(gw2ApiToken);
if (!gw2TokenInfo.permissions().contains(Gw2ApiPermission.ACCOUNT)) {
throw new ApiTokenServiceException(ApiTokenServiceException.MISSING_ACCOUNT_PERMISSION, HttpStatus.BAD_REQUEST);
}
final Gw2Account gw2Account = this.gw2ApiService.getAccount(gw2ApiToken);
if (!gw2Account.id().equals(gw2AccountId)) {
throw new ApiTokenServiceException(ApiTokenServiceException.GW2_ACCOUNT_ID_MISMATCH, HttpStatus.BAD_REQUEST);
}
apiTokenEntity = apiTokenEntity.withGw2ApiToken(gw2ApiToken).withGw2ApiPermissions(gw2TokenInfo.permissions().stream().map(Gw2ApiPermission::gw2).collect(Collectors.toSet())).withLastValidCheckTime(this.clock.instant(), true);
}
if (displayName != null) {
apiTokenEntity = apiTokenEntity.withDisplayName(displayName);
}
return ApiToken.fromEntity(this.apiTokenRepository.save(apiTokenEntity));
}
use of com.gw2auth.oauth2.server.service.Gw2ApiPermission in project oauth2-server by gw2auth.
the class ClientAuthorizationControllerTest method getClientAuthorizations.
@WithGw2AuthLogin
public void getClientAuthorizations(MockHttpSession session) throws Exception {
final long accountId = AuthenticationHelper.getUser(session).orElseThrow().getAccountId();
// create client
final ClientRegistrationEntity client = this.testHelper.createClientRegistration(accountId, "Client");
// create consent
this.testHelper.createClientConsent(accountId, client.id(), Set.of(Gw2ApiPermission.ACCOUNT.oauth2(), ClientConsentService.GW2AUTH_VERIFIED_SCOPE));
// create 2 authorizations
final ClientAuthorizationEntity authorization1 = this.testHelper.createClientAuthorization(accountId, client.id(), Set.of(Gw2ApiPermission.ACCOUNT.oauth2()));
final ClientAuthorizationEntity authorization2 = this.testHelper.createClientAuthorization(accountId, client.id(), Set.of(Gw2ApiPermission.ACCOUNT.oauth2(), ClientConsentService.GW2AUTH_VERIFIED_SCOPE));
// insert tokens for these authorizations
final ApiTokenEntity tokenA = this.testHelper.createApiToken(accountId, UUID.randomUUID(), Gw2ApiPermission.all(), "Token A");
final ApiTokenEntity tokenB = this.testHelper.createApiToken(accountId, UUID.randomUUID(), Gw2ApiPermission.all(), "Token B");
final ApiTokenEntity tokenC = this.testHelper.createApiToken(accountId, UUID.randomUUID(), Gw2ApiPermission.all(), "Token C");
final ApiTokenEntity tokenD = this.testHelper.createApiToken(accountId, UUID.randomUUID(), Gw2ApiPermission.all(), "Token D");
this.testHelper.createClientAuthorizationTokens(accountId, authorization1.id(), tokenA.gw2AccountId(), tokenD.gw2AccountId());
this.testHelper.createClientAuthorizationTokens(accountId, authorization2.id(), tokenA.gw2AccountId(), tokenB.gw2AccountId(), tokenC.gw2AccountId());
// query api
final String jsonResponse = this.mockMvc.perform(get("/api/client/authorization/{clientId}", client.clientId()).session(session)).andExpect(status().isOk()).andReturn().getResponse().getContentAsString();
final ObjectMapper mapper = new ObjectMapper();
final JsonNode node = mapper.readTree(jsonResponse);
assertTrue(node.isArray());
assertEquals(2, node.size());
for (int i = 0; i < node.size(); i++) {
final JsonNode authorizationNode = node.get(i);
final String id = authorizationNode.get("id").textValue();
final ClientAuthorizationEntity authorization;
final Map<UUID, ApiTokenEntity> apiTokens;
if (id.equals(authorization1.id())) {
authorization = authorization1;
apiTokens = Map.of(tokenA.gw2AccountId(), tokenA, tokenD.gw2AccountId(), tokenD);
} else if (id.equals(authorization2.id())) {
authorization = authorization2;
apiTokens = Map.of(tokenA.gw2AccountId(), tokenA, tokenB.gw2AccountId(), tokenB, tokenC.gw2AccountId(), tokenC);
} else {
fail("unknown authorization id found in response");
throw new IllegalStateException("");
}
assertInstantEquals(authorization.creationTime(), authorizationNode.get("creationTime").textValue());
assertInstantEquals(authorization.lastUpdateTime(), authorizationNode.get("lastUpdateTime").textValue());
assertEquals(authorization.displayName(), authorizationNode.get("displayName").textValue());
// authorized scopes
final Set<String> expectedAuthorizedScopes = new HashSet<>(authorization.authorizedScopes());
final JsonNode gw2ApiPermissionsNode = authorizationNode.get("authorizedGw2ApiPermissions");
assertTrue(gw2ApiPermissionsNode.isArray());
for (int j = 0; j < gw2ApiPermissionsNode.size(); j++) {
final String gw2ApiPermissionStr = gw2ApiPermissionsNode.get(j).textValue();
final Gw2ApiPermission gw2ApiPermission = Gw2ApiPermission.fromGw2(gw2ApiPermissionStr).orElseThrow();
if (!expectedAuthorizedScopes.remove(gw2ApiPermission.oauth2())) {
fail("received gw2 api permission which is not present in the entity");
}
}
if (authorizationNode.get("authorizedVerifiedInformation").booleanValue()) {
if (!expectedAuthorizedScopes.remove(ClientConsentService.GW2AUTH_VERIFIED_SCOPE)) {
fail("received verified scope but it is not present in the entity");
}
}
assertTrue(expectedAuthorizedScopes.isEmpty());
// tokens
final Map<UUID, ApiTokenEntity> expectedApiTokens = new HashMap<>(apiTokens);
final JsonNode tokensNode = authorizationNode.get("tokens");
assertTrue(tokensNode.isArray());
for (int j = 0; j < tokensNode.size(); j++) {
final JsonNode tokenNode = tokensNode.get(j);
final ApiTokenEntity expectedApiToken = expectedApiTokens.remove(UUID.fromString(tokenNode.get("gw2AccountId").textValue()));
assertNotNull(expectedApiToken);
assertEquals(expectedApiToken.displayName(), tokenNode.get("displayName").textValue());
}
assertTrue(expectedApiTokens.isEmpty());
}
}
use of com.gw2auth.oauth2.server.service.Gw2ApiPermission in project oauth2-server by gw2auth.
the class ClientConsentControllerTest method getClientConsents.
@WithGw2AuthLogin
public void getClientConsents(MockHttpSession session) throws Exception {
final long accountId = AuthenticationHelper.getUser(session).orElseThrow().getAccountId();
final ClientRegistrationEntity clientRegistrationA = this.testHelper.createClientRegistration(accountId, "Name");
final ClientRegistrationEntity clientRegistrationC = this.testHelper.createClientRegistration(accountId, "Name");
final ClientConsentEntity clientConsentA = this.testHelper.createClientConsent(accountId, clientRegistrationA.id(), Set.of(Gw2ApiPermission.ACCOUNT.oauth2(), ClientConsentService.GW2AUTH_VERIFIED_SCOPE));
final ClientConsentEntity clientConsentB = this.testHelper.createClientConsent(accountId, clientRegistrationC.id(), Set.of(Gw2ApiPermission.ACCOUNT.oauth2(), Gw2ApiPermission.GUILDS.oauth2()));
final String jsonResponse = this.mockMvc.perform(get("/api/client/consent").session(session)).andExpect(status().isOk()).andExpect(jsonPath("$.length()").value(2)).andReturn().getResponse().getContentAsString();
final ObjectMapper mapper = new ObjectMapper();
final JsonNode node = mapper.readTree(jsonResponse);
assertTrue(node.isArray());
boolean foundAuthorizationA = false;
boolean foundAuthorizationC = false;
for (int i = 0; i < node.size(); i++) {
final JsonNode element = node.get(i);
final JsonNode clientRegistrationNode = element.get("clientRegistration");
final ClientRegistrationEntity clientRegistration;
final ClientConsentEntity clientConsent;
if (clientRegistrationNode.get("clientId").textValue().equals(clientRegistrationA.clientId().toString())) {
if (foundAuthorizationA) {
fail("authorization A appeared at least twice in the response");
return;
} else {
foundAuthorizationA = true;
clientRegistration = clientRegistrationA;
clientConsent = clientConsentA;
}
} else if (clientRegistrationNode.get("clientId").textValue().equals(clientRegistrationC.clientId().toString())) {
if (foundAuthorizationC) {
fail("authorization C appeared at least twice in the response");
return;
} else {
foundAuthorizationC = true;
clientRegistration = clientRegistrationC;
clientConsent = clientConsentB;
}
} else {
fail("unknown authorization appeared in response");
return;
}
// registration
assertInstantEquals(clientRegistration.creationTime(), clientRegistrationNode.get("creationTime").textValue());
assertEquals(clientRegistration.displayName(), clientRegistrationNode.get("displayName").textValue());
// accountsub
assertEquals(clientConsent.accountSub().toString(), element.get("accountSub").textValue());
// authorized scopes
final Set<String> expectedScopes = new HashSet<>(clientConsent.authorizedScopes());
final JsonNode authorizedGw2ApiPermissionsNode = element.get("authorizedGw2ApiPermissions");
assertTrue(authorizedGw2ApiPermissionsNode.isArray());
for (int j = 0; j < authorizedGw2ApiPermissionsNode.size(); j++) {
final Gw2ApiPermission gw2ApiPermission = Gw2ApiPermission.fromGw2(authorizedGw2ApiPermissionsNode.get(j).textValue()).orElseThrow();
if (!expectedScopes.remove(gw2ApiPermission.oauth2())) {
fail("got unexpected scope in authorization");
}
}
if (element.get("authorizedVerifiedInformation").booleanValue()) {
if (!expectedScopes.remove(ClientConsentService.GW2AUTH_VERIFIED_SCOPE)) {
fail("got unexpected scope in authorization");
}
}
assertTrue(expectedScopes.isEmpty());
}
assertTrue(foundAuthorizationA);
assertTrue(foundAuthorizationC);
}
use of com.gw2auth.oauth2.server.service.Gw2ApiPermission in project oauth2-server by gw2auth.
the class ApiTokenControllerTest method prepareGw2RestServerForTokenInfoRequest.
private void prepareGw2RestServerForTokenInfoRequest(String gw2ApiToken, String apiTokenName, Set<Gw2ApiPermission> gw2ApiPermissions) {
this.gw2RestServer.expect(requestTo(new StringStartsWith("/v2/tokeninfo"))).andExpect(method(HttpMethod.GET)).andExpect(MockRestRequestMatchers.header("Authorization", new IsEqual<>("Bearer " + gw2ApiToken))).andRespond((request) -> {
final MockClientHttpResponse response = new MockClientHttpResponse(new JSONObject(Map.of("name", apiTokenName, "permissions", gw2ApiPermissions.stream().map(Gw2ApiPermission::gw2).collect(Collectors.toList()))).toString().getBytes(StandardCharsets.UTF_8), HttpStatus.OK);
response.getHeaders().setContentType(MediaType.APPLICATION_JSON);
return response;
});
}
Aggregations