From 3ffeeced84ff8cc80fd9332cb6b136bdb7824cac Mon Sep 17 00:00:00 2001 From: Lukasz Gawel Date: Fri, 10 Jan 2025 16:32:28 +0100 Subject: [PATCH] fix: tokens should be removed when user is disabled fixes AM-4062 (cherry picked from commit d3e0ba61bc61ff6c78ef1d631baa28ed97741848) --- .../scim/service/impl/UserServiceImpl.java | 14 +- .../handler/scim/service/UserServiceTest.java | 52 ++++++++ .../organizations/OrganizationResource.java | 1 + .../environments/domains/UserResource.java | 2 +- .../users/OrganizationUserResource.java | 9 +- .../api/resources/UserResourceTest.java | 32 ++++- .../service/OrganizationUserService.java | 2 + .../am/management/service/UserService.java | 7 - .../service/impl/AbstractUserService.java | 4 +- .../impl/OrganizationUserServiceImpl.java | 5 + .../service/impl/UserServiceImpl.java | 19 ++- .../main/java/io/gravitee/am/model/User.java | 13 ++ .../specs/gateway/refresh-token.jest.spec.ts | 125 ++++++++++++++++++ 13 files changed, 262 insertions(+), 23 deletions(-) create mode 100644 gravitee-am-test/specs/gateway/refresh-token.jest.spec.ts diff --git a/gravitee-am-gateway/gravitee-am-gateway-handler/gravitee-am-gateway-handler-scim/src/main/java/io/gravitee/am/gateway/handler/scim/service/impl/UserServiceImpl.java b/gravitee-am-gateway/gravitee-am-gateway-handler/gravitee-am-gateway-handler-scim/src/main/java/io/gravitee/am/gateway/handler/scim/service/impl/UserServiceImpl.java index 66ca826497e..c316ca7e0e6 100644 --- a/gravitee-am-gateway/gravitee-am-gateway-handler/gravitee-am-gateway-handler-scim/src/main/java/io/gravitee/am/gateway/handler/scim/service/impl/UserServiceImpl.java +++ b/gravitee-am-gateway/gravitee-am-gateway-handler/gravitee-am-gateway-handler-scim/src/main/java/io/gravitee/am/gateway/handler/scim/service/impl/UserServiceImpl.java @@ -49,6 +49,7 @@ import io.gravitee.am.service.PasswordService; import io.gravitee.am.service.RateLimiterService; import io.gravitee.am.service.RoleService; +import io.gravitee.am.service.TokenService; import io.gravitee.am.service.UserActivityService; import io.gravitee.am.service.VerifyAttemptService; import io.gravitee.am.service.exception.AbstractManagementException; @@ -151,6 +152,9 @@ public class UserServiceImpl implements UserService { @Autowired private PasswordPolicyManager passwordPolicyManager; + @Autowired + private TokenService tokenService; + @Override public Single> list(Filter filter, int startIndex, int size, String baseUrl) { LOGGER.debug("Find users by domain: {}", domain.getId()); @@ -365,7 +369,7 @@ public Single innerUpdate(io.gravitee.am.model.User userIntoDb, User scimU userToUpdate.setLastPasswordReset(new Date()); } - return userRepository.update(userToUpdate, UpdateActions.build(existingUser, userToUpdate)); + return updateUser(userToUpdate, UpdateActions.build(existingUser, userToUpdate)); }) .onErrorResumeNext(ex -> { if (ex instanceof UserNotFoundException || @@ -374,7 +378,7 @@ public Single innerUpdate(io.gravitee.am.model.User userIntoDb, User scimU // idp user does not exist, only update AM user // clear password userToUpdate.setPassword(null); - return userRepository.update(userToUpdate, UpdateActions.build(existingUser, userToUpdate)); + return updateUser(userToUpdate, UpdateActions.build(existingUser, userToUpdate)); } return Single.error(ex); }) @@ -403,6 +407,12 @@ public Single innerUpdate(io.gravitee.am.model.User userIntoDb, User scimU }); } + private Single updateUser(io.gravitee.am.model.User userToUpdate, UpdateActions updateActions){ + Completable revokeTokens = userToUpdate.isDisabled() ? + tokenService.deleteByUser(userToUpdate) : Completable.complete(); + return revokeTokens.andThen(userRepository.update(userToUpdate, updateActions)); + } + @Override public Single patch(String userId, PatchOp patchOp, String idp, String baseUrl, io.gravitee.am.identityprovider.api.User principal, Client client) { LOGGER.debug("Patch user {}", userId); diff --git a/gravitee-am-gateway/gravitee-am-gateway-handler/gravitee-am-gateway-handler-scim/src/test/java/io/gravitee/am/gateway/handler/scim/service/UserServiceTest.java b/gravitee-am-gateway/gravitee-am-gateway-handler/gravitee-am-gateway-handler-scim/src/test/java/io/gravitee/am/gateway/handler/scim/service/UserServiceTest.java index 695c8b3404a..456c1d8d5c8 100644 --- a/gravitee-am-gateway/gravitee-am-gateway-handler/gravitee-am-gateway-handler-scim/src/test/java/io/gravitee/am/gateway/handler/scim/service/UserServiceTest.java +++ b/gravitee-am-gateway/gravitee-am-gateway-handler/gravitee-am-gateway-handler-scim/src/test/java/io/gravitee/am/gateway/handler/scim/service/UserServiceTest.java @@ -48,6 +48,7 @@ import io.gravitee.am.service.PasswordService; import io.gravitee.am.service.RateLimiterService; import io.gravitee.am.service.RoleService; +import io.gravitee.am.service.TokenService; import io.gravitee.am.service.UserActivityService; import io.gravitee.am.service.VerifyAttemptService; import io.gravitee.am.service.exception.UserInvalidException; @@ -55,6 +56,7 @@ import io.gravitee.am.service.validators.email.EmailValidatorImpl; import io.gravitee.am.service.validators.user.UserValidator; import io.gravitee.am.service.validators.user.UserValidatorImpl; +import io.reactivex.rxjava3.core.Completable; import io.reactivex.rxjava3.core.Flowable; import io.reactivex.rxjava3.core.Maybe; import io.reactivex.rxjava3.core.Single; @@ -164,6 +166,8 @@ public class UserServiceTest { private static final String DOMAIN_ID = "domain"; + @Mock + private TokenService tokenService; @Before public void setUp() { @@ -390,9 +394,55 @@ public void shouldUpdateUser_status_enabled() { verify(userProvider).create(any()); verify(userProvider, never()).update(anyString(), any()); verify(userProvider, never()).updatePassword(any(), eq(PASSWORD)); + verify(tokenService, never()).deleteByUser(any()); assertTrue(userCaptor.getValue().isEnabled()); } + @Test + public void shouldUpdateUser_status_disabled_and_tokens_revoked() { + io.gravitee.am.model.User existingUser = mock(io.gravitee.am.model.User.class); + when(existingUser.getId()).thenReturn("user-id"); + when(existingUser.getSource()).thenReturn("user-idp"); + when(existingUser.getUsername()).thenReturn("username"); + + User scimUser = mock(User.class); + when(scimUser.getPassword()).thenReturn(PASSWORD); + when(scimUser.isActive()).thenReturn(false); + + io.gravitee.am.identityprovider.api.User idpUser = mock(io.gravitee.am.identityprovider.api.User.class); + + UserProvider userProvider = mock(UserProvider.class); + when(userProvider.create(any())).thenReturn(Single.just(idpUser)); + + Set roles = new HashSet<>(); + Role role1 = new Role(); + role1.setId("role-1"); + Role role2 = new Role(); + role2.setId("role-2"); + roles.add(role1); + roles.add(role2); + + when(userRepository.findById(existingUser.getId())).thenReturn(Maybe.just(existingUser)); + when(identityProviderManager.getUserProvider(anyString())).thenReturn(Maybe.just(userProvider)); + when(identityProviderManager.getIdentityProvider(anyString())).thenReturn(new IdentityProvider()); + when(tokenService.deleteByUser(any())).thenReturn(Completable.complete()); + ArgumentCaptor userCaptor = ArgumentCaptor.forClass(io.gravitee.am.model.User.class); + when(userRepository.update(any(), any())).thenReturn(Single.just(existingUser)); + when(groupService.findByMember(existingUser.getId())).thenReturn(Flowable.empty()); + when(passwordService.isValid(eq(PASSWORD), any(), any())).thenReturn(true); + + TestObserver testObserver = userService.update(existingUser.getId(), scimUser, null, "/", null, null).test(); + testObserver.assertNoErrors(); + testObserver.assertComplete(); + + verify(userRepository, times(1)).update(userCaptor.capture(), any()); + verify(userProvider).create(any()); + verify(userProvider, never()).update(anyString(), any()); + verify(userProvider, never()).updatePassword(any(), eq(PASSWORD)); + verify(tokenService, times(1)).deleteByUser(any()); + assertFalse(userCaptor.getValue().isEnabled()); + } + @Test public void shouldUpdateUser_roles_entitlements() { io.gravitee.am.model.User existingUser = new io.gravitee.am.model.User(); @@ -579,6 +629,7 @@ public void shouldPatchUser() throws Exception { when(userRepository.findById(userId)).thenReturn(Maybe.just(patchedUser)); when(identityProviderManager.getIdentityProvider(anyString())).thenReturn(new IdentityProvider()); when(identityProviderManager.getUserProvider(anyString())).thenReturn(Maybe.just(userProvider)); + when(tokenService.deleteByUser(any())).thenReturn(Completable.complete()); doAnswer(invocation -> { io.gravitee.am.model.User userToUpdate = invocation.getArgument(0); Assert.assertEquals("my user 2", userToUpdate.getDisplayName()); @@ -671,6 +722,7 @@ public void shouldPatchUser_customGraviteeUser() throws Exception { when(userRepository.findById(userId)).thenReturn(Maybe.just(patchedUser)); when(identityProviderManager.getIdentityProvider(anyString())).thenReturn(new IdentityProvider()); when(identityProviderManager.getUserProvider(anyString())).thenReturn(Maybe.just(userProvider)); + when(tokenService.deleteByUser(any())).thenReturn(Completable.complete()); doAnswer(invocation -> { io.gravitee.am.model.User userToUpdate = invocation.getArgument(0); Assert.assertTrue(userToUpdate.getAdditionalInformation().containsKey("customClaim")); diff --git a/gravitee-am-management-api/gravitee-am-management-api-rest/src/main/java/io/gravitee/am/management/handlers/management/api/resources/organizations/OrganizationResource.java b/gravitee-am-management-api/gravitee-am-management-api-rest/src/main/java/io/gravitee/am/management/handlers/management/api/resources/organizations/OrganizationResource.java index a30e1a4441d..22925ca76fd 100644 --- a/gravitee-am-management-api/gravitee-am-management-api-rest/src/main/java/io/gravitee/am/management/handlers/management/api/resources/organizations/OrganizationResource.java +++ b/gravitee-am-management-api/gravitee-am-management-api-rest/src/main/java/io/gravitee/am/management/handlers/management/api/resources/organizations/OrganizationResource.java @@ -27,6 +27,7 @@ import io.gravitee.am.management.handlers.management.api.resources.organizations.settings.SettingsResource; import io.gravitee.am.management.handlers.management.api.resources.organizations.tags.TagsResource; import io.gravitee.am.management.handlers.management.api.resources.organizations.users.OrganizationUsersResource; + import jakarta.ws.rs.Path; import jakarta.ws.rs.container.ResourceContext; import jakarta.ws.rs.core.Context; diff --git a/gravitee-am-management-api/gravitee-am-management-api-rest/src/main/java/io/gravitee/am/management/handlers/management/api/resources/organizations/environments/domains/UserResource.java b/gravitee-am-management-api/gravitee-am-management-api-rest/src/main/java/io/gravitee/am/management/handlers/management/api/resources/organizations/environments/domains/UserResource.java index 6f8b2b434bb..36315fc5b11 100644 --- a/gravitee-am-management-api/gravitee-am-management-api-rest/src/main/java/io/gravitee/am/management/handlers/management/api/resources/organizations/environments/domains/UserResource.java +++ b/gravitee-am-management-api/gravitee-am-management-api-rest/src/main/java/io/gravitee/am/management/handlers/management/api/resources/organizations/environments/domains/UserResource.java @@ -182,7 +182,7 @@ public void updateUserStatus( checkAnyPermission(organizationId, environmentId, domain, Permission.DOMAIN_USER, Acl.UPDATE) .andThen(domainService.findById(domain) .switchIfEmpty(Maybe.error(new DomainNotFoundException(domain))) - .flatMapSingle(irrelevant -> userService.updateStatus(ReferenceType.DOMAIN, domain, user, status.isEnabled(), authenticatedUser))) + .flatMapSingle(irrelevant -> userService.updateStatus(domain, user, status.isEnabled(), authenticatedUser))) .subscribe(response::resume, response::resume); } diff --git a/gravitee-am-management-api/gravitee-am-management-api-rest/src/main/java/io/gravitee/am/management/handlers/management/api/resources/organizations/users/OrganizationUserResource.java b/gravitee-am-management-api/gravitee-am-management-api-rest/src/main/java/io/gravitee/am/management/handlers/management/api/resources/organizations/users/OrganizationUserResource.java index f05f7b1256c..ccfe7c4b41a 100644 --- a/gravitee-am-management-api/gravitee-am-management-api-rest/src/main/java/io/gravitee/am/management/handlers/management/api/resources/organizations/users/OrganizationUserResource.java +++ b/gravitee-am-management-api/gravitee-am-management-api-rest/src/main/java/io/gravitee/am/management/handlers/management/api/resources/organizations/users/OrganizationUserResource.java @@ -53,9 +53,7 @@ import jakarta.ws.rs.PathParam; import jakarta.ws.rs.Produces; import jakarta.ws.rs.container.AsyncResponse; -import jakarta.ws.rs.container.ResourceContext; import jakarta.ws.rs.container.Suspended; -import jakarta.ws.rs.core.Context; import jakarta.ws.rs.core.Response; import org.springframework.beans.factory.annotation.Autowired; @@ -69,9 +67,6 @@ @SuppressWarnings("ResultOfMethodCallIgnored") public class OrganizationUserResource extends AbstractResource { - @Context - private ResourceContext resourceContext; - @Autowired @Named("managementOrganizationUserService") private OrganizationUserService organizationUserService; @@ -216,8 +211,8 @@ public void updateUserStatus( final io.gravitee.am.identityprovider.api.User authenticatedUser = getAuthenticatedUser(); checkPermission(ReferenceType.ORGANIZATION, organizationId, Permission.ORGANIZATION_USER, Acl.UPDATE) - .andThen(organizationUserService.updateStatus(ReferenceType.ORGANIZATION, organizationId, user, status.isEnabled(), authenticatedUser) - .map(UserEntity::new)) + .andThen(organizationUserService.updateStatus(organizationId, user, status.isEnabled(), authenticatedUser) + .map(UserEntity::new)) .subscribe(response::resume, response::resume); } diff --git a/gravitee-am-management-api/gravitee-am-management-api-rest/src/test/java/io/gravitee/am/management/handlers/management/api/resources/UserResourceTest.java b/gravitee-am-management-api/gravitee-am-management-api-rest/src/test/java/io/gravitee/am/management/handlers/management/api/resources/UserResourceTest.java index 5cccedbd7fe..1bf13fb1100 100644 --- a/gravitee-am-management-api/gravitee-am-management-api-rest/src/test/java/io/gravitee/am/management/handlers/management/api/resources/UserResourceTest.java +++ b/gravitee-am-management-api/gravitee-am-management-api-rest/src/test/java/io/gravitee/am/management/handlers/management/api/resources/UserResourceTest.java @@ -53,6 +53,7 @@ import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.eq; import static org.mockito.Mockito.doReturn; +import static org.mockito.Mockito.times; /** * @author Titouan COMPIEGNE (titouan.compiegne at graviteesource.com) @@ -301,7 +302,34 @@ public void shouldNotUpdateUsername_domainNotFound() { } @Test - public void shouldUpdateStatus() { + public void shouldUpdateStatus_enabled() { + final String domainId = "domain-id"; + final Domain mockDomain = new Domain(); + mockDomain.setId(domainId); + + final String userId = "userId"; + final User mockUser = new User(); + mockUser.setId(userId); + mockUser.setUsername("user-username"); + mockUser.setReferenceType(ReferenceType.DOMAIN); + mockUser.setReferenceId(domainId); + mockUser.setEnabled(false); + + var statusEntity = new StatusEntity(); + statusEntity.setEnabled(false); + doReturn(Maybe.just(mockDomain)).when(domainService).findById(domainId); + doReturn(Single.just(mockUser)).when(userService).updateStatus(eq(ReferenceType.DOMAIN), eq(domainId), eq(userId), eq(statusEntity.isEnabled()), any()); + + final Response response = target("domains").path(domainId).path("users").path(userId).path("status").request().put(Entity.json(statusEntity)); + assertEquals(HttpStatusCode.OK_200, response.getStatus()); + final User user = readEntity(response, User.class); + assertEquals(domainId, user.getReferenceId()); + assertEquals(statusEntity.isEnabled(), user.isEnabled()); + Mockito.verifyNoInteractions(tokenService); + } + + @Test + public void shouldUpdateStatus_disabled() { final String domainId = "domain-id"; final Domain mockDomain = new Domain(); mockDomain.setId(domainId); @@ -353,6 +381,8 @@ public void shouldUpdateStatus_organization() { assertEquals(mockUser.getUsername(), user.getUsername()); assertNull(user.getPassword()); assertEquals(statusEntity.isEnabled(), user.isEnabled()); + Mockito.verifyNoInteractions(tokenService); + } @Test diff --git a/gravitee-am-management-api/gravitee-am-management-api-service/src/main/java/io/gravitee/am/management/service/OrganizationUserService.java b/gravitee-am-management-api/gravitee-am-management-api-service/src/main/java/io/gravitee/am/management/service/OrganizationUserService.java index de4559903cd..82e6c5e539f 100644 --- a/gravitee-am-management-api/gravitee-am-management-api-service/src/main/java/io/gravitee/am/management/service/OrganizationUserService.java +++ b/gravitee-am-management-api/gravitee-am-management-api-service/src/main/java/io/gravitee/am/management/service/OrganizationUserService.java @@ -47,4 +47,6 @@ public interface OrganizationUserService extends CommonUserService { Single findByAccessToken(String tokenId, String tokenValue); Maybe revokeToken(String organizationId, String userId, String tokenId, io.gravitee.am.identityprovider.api.User authenticatedUser); + Single updateStatus(String organizationId, String id, boolean status, io.gravitee.am.identityprovider.api.User principal); + } diff --git a/gravitee-am-management-api/gravitee-am-management-api-service/src/main/java/io/gravitee/am/management/service/UserService.java b/gravitee-am-management-api/gravitee-am-management-api-service/src/main/java/io/gravitee/am/management/service/UserService.java index b66707e4b2b..1188bb1850a 100644 --- a/gravitee-am-management-api/gravitee-am-management-api-service/src/main/java/io/gravitee/am/management/service/UserService.java +++ b/gravitee-am-management-api/gravitee-am-management-api-service/src/main/java/io/gravitee/am/management/service/UserService.java @@ -64,9 +64,6 @@ default Single update(String domain, String id, UpdateUser updateUser) { return update(domain, id, updateUser, null); } - default Single updateStatus(String domain, String userId, boolean status) { - return updateStatus(domain, userId, status, null); - } default Completable unlock(ReferenceType referenceType, String referenceId, String userId) { return unlock(referenceType, referenceId, userId, null); } @@ -79,8 +76,4 @@ default Single revokeRoles(ReferenceType referenceType, String referenceId return revokeRoles(referenceType, referenceId, userId, roles, null); } - default Single enrollFactors(String userId, List factors) { - return enrollFactors(userId, factors, null); - } - } diff --git a/gravitee-am-management-api/gravitee-am-management-api-service/src/main/java/io/gravitee/am/management/service/impl/AbstractUserService.java b/gravitee-am-management-api/gravitee-am-management-api-service/src/main/java/io/gravitee/am/management/service/impl/AbstractUserService.java index 0ecf83f4877..c94f324cfb7 100644 --- a/gravitee-am-management-api/gravitee-am-management-api-service/src/main/java/io/gravitee/am/management/service/impl/AbstractUserService.java +++ b/gravitee-am-management-api/gravitee-am-management-api-service/src/main/java/io/gravitee/am/management/service/impl/AbstractUserService.java @@ -168,9 +168,9 @@ private Single updateWithUserProvider(UpdateUser updateUser, User } @Override - public Single updateStatus(ReferenceType referenceType, String referenceId, String id, boolean status, io. + public Single updateStatus(ReferenceType referenceType, String referenceId, String userId, boolean status, io. gravitee.am.identityprovider.api.User principal) { - return getUserService().findById(referenceType, referenceId, id) + return getUserService().findById(referenceType, referenceId, userId) .flatMap(user -> { user.setEnabled(status); return getUserService().update(user); diff --git a/gravitee-am-management-api/gravitee-am-management-api-service/src/main/java/io/gravitee/am/management/service/impl/OrganizationUserServiceImpl.java b/gravitee-am-management-api/gravitee-am-management-api-service/src/main/java/io/gravitee/am/management/service/impl/OrganizationUserServiceImpl.java index 71309851b1b..988bdc18585 100644 --- a/gravitee-am-management-api/gravitee-am-management-api-service/src/main/java/io/gravitee/am/management/service/impl/OrganizationUserServiceImpl.java +++ b/gravitee-am-management-api/gravitee-am-management-api-service/src/main/java/io/gravitee/am/management/service/impl/OrganizationUserServiceImpl.java @@ -53,6 +53,7 @@ import static io.gravitee.am.management.service.impl.IdentityProviderManagerImpl.IDP_GRAVITEE; import static org.springframework.util.StringUtils.hasText; +import static io.gravitee.am.model.ReferenceType.ORGANIZATION; /** * @author Titouan COMPIEGNE (titouan.compiegne at graviteesource.com) @@ -275,4 +276,8 @@ public Single delete(ReferenceType referenceType, String referenceId, Stri return super.delete(referenceType, referenceId, userId, principal) .flatMap(user -> getUserService().revokeUserAccessTokens(user.getReferenceType(), user.getReferenceId(), user.getId()).toSingleDefault(user)); } + + public Single updateStatus(String organizationId, String userId, boolean status, io.gravitee.am.identityprovider.api.User principal) { + return updateStatus(ORGANIZATION, organizationId, userId, status, principal); + } } diff --git a/gravitee-am-management-api/gravitee-am-management-api-service/src/main/java/io/gravitee/am/management/service/impl/UserServiceImpl.java b/gravitee-am-management-api/gravitee-am-management-api-service/src/main/java/io/gravitee/am/management/service/impl/UserServiceImpl.java index a21857590e9..a21fd7e3549 100644 --- a/gravitee-am-management-api/gravitee-am-management-api-service/src/main/java/io/gravitee/am/management/service/impl/UserServiceImpl.java +++ b/gravitee-am-management-api/gravitee-am-management-api-service/src/main/java/io/gravitee/am/management/service/impl/UserServiceImpl.java @@ -109,7 +109,7 @@ public class UserServiceImpl extends AbstractUserService delete(ReferenceType referenceType, String referenceId, Stri } @Override - public Single updateStatus(String domain, String id, boolean status, io.gravitee.am.identityprovider.api.User principal) { - return updateStatus(DOMAIN, domain, id, status, principal); + public Single updateStatus(String domainId, String userId, boolean status, io.gravitee.am.identityprovider.api.User principal) { + return updateStatus(DOMAIN, domainId, userId, status, principal); + } + + @Override + public Single updateStatus(ReferenceType referenceType, String referenceId, String userId, boolean status, io. + gravitee.am.identityprovider.api.User principal) { + Completable removeTokens = status ? Completable.complete() : tokenService.deleteByUser(User.simpleUser(userId, referenceType, referenceId)); + return getUserService().findById(referenceType, referenceId, userId) + .flatMap(user -> { + user.setEnabled(status); + return removeTokens.andThen(getUserService().update(user)); + }) + .doOnSuccess(user1 -> auditService.report(AuditBuilder.builder(UserAuditBuilder.class).principal(principal).type((status ? EventType.USER_ENABLED : EventType.USER_DISABLED)).user(user1))) + .doOnError(throwable -> auditService.report(AuditBuilder.builder(UserAuditBuilder.class).principal(principal).type((status ? EventType.USER_ENABLED : EventType.USER_DISABLED)).throwable(throwable))); } @Override diff --git a/gravitee-am-model/src/main/java/io/gravitee/am/model/User.java b/gravitee-am-model/src/main/java/io/gravitee/am/model/User.java index 2b0de35f688..2957ff237c5 100644 --- a/gravitee-am-model/src/main/java/io/gravitee/am/model/User.java +++ b/gravitee-am-model/src/main/java/io/gravitee/am/model/User.java @@ -547,4 +547,17 @@ public void unlockUser() { setAccountLockedAt(null); setAccountLockedUntil(null); } + + public boolean isDisabled(){ + return Boolean.FALSE.equals(enabled); + } + + public static User simpleUser(String userId, ReferenceType referenceType, String referenceId) { + User user = new User(); + user.setId(userId); + user.setReferenceType(referenceType); + user.setReferenceId(referenceId); + return user; + } + } diff --git a/gravitee-am-test/specs/gateway/refresh-token.jest.spec.ts b/gravitee-am-test/specs/gateway/refresh-token.jest.spec.ts new file mode 100644 index 00000000000..052cd46cf07 --- /dev/null +++ b/gravitee-am-test/specs/gateway/refresh-token.jest.spec.ts @@ -0,0 +1,125 @@ +/* + * Copyright (C) 2015 The Gravitee team (http://gravitee.io) + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +import fetch from 'cross-fetch'; +import * as faker from 'faker'; +import { afterAll, beforeAll, expect, jest } from '@jest/globals'; +import { createDomain, deleteDomain, startDomain } from '@management-commands/domain-management-commands'; +import { buildCreateAndTestUser, updateUserStatus } from '@management-commands/user-management-commands'; + +import { requestAdminAccessToken } from '@management-commands/token-management-commands'; +import { getWellKnownOpenIdConfiguration, performPost } from '@gateway-commands/oauth-oidc-commands'; +import { createTestApp } from '@utils-commands/application-commands'; +import { getAllIdps } from '@management-commands/idp-management-commands'; +import { applicationBase64Token } from '@gateway-commands/utils'; + +global.fetch = fetch; + +let accessToken; +let domain; +let defaultIdp; +let client; +let user; +let oidc; + +jest.setTimeout(200000); + +beforeAll(async () => { + const adminTokenResponse = await requestAdminAccessToken(); + accessToken = adminTokenResponse.body.access_token; + expect(accessToken).toBeDefined(); + + const createdDomain = await createDomain(accessToken, 'refresh-tokens-domain', faker.company.catchPhraseDescriptor()); + expect(createdDomain).toBeDefined(); + expect(createdDomain.id).toBeDefined(); + + const idpSet = await getAllIdps(createdDomain.id, accessToken); + defaultIdp = idpSet.values().next().value; + + client = await createTestApp('webapp', createdDomain, accessToken, 'WEB', { + settings: { + oauth: { + redirectUris: ['https://auth-nightly.gravitee.io/myApp/callback'], + grantTypes: ['authorization_code', 'password', 'refresh_token'], + scopeSettings: [ + { scope: 'openid', defaultScope: true }, + { scope: 'email', defaultScope: true }, + { scope: 'profile', defaultScope: true }, + ], + }, + }, + identityProviders: [{ identity: defaultIdp.id, priority: 0 }], + }); + + const domainStarted = await startDomain(createdDomain.id, accessToken); + expect(domainStarted).toBeDefined(); + expect(domainStarted.id).toEqual(createdDomain.id); + + domain = domainStarted; + await new Promise((r) => setTimeout(r, 10000)); + + const result = await getWellKnownOpenIdConfiguration(domain.hrid).expect(200); + oidc = result.body; + + user = await buildCreateAndTestUser(domain.id, accessToken, 0); +}); + +afterAll(async () => { + if (domain?.id) { + await deleteDomain(domain.id, accessToken); + } +}); + +describe('when user is enabled', () => { + let tokens; + + it('they can obtain access_token and refresh_token', async () => { + let response = await performPost(oidc.token_endpoint, '', `grant_type=password&username=${user.username}&password=SomeP@ssw0rd`, { + 'Content-type': 'application/x-www-form-urlencoded', + Authorization: 'Basic ' + applicationBase64Token(client), + }).expect(200); + tokens = response.body; + }); + + it('and the new token can be obtained by refresh_token', async () => { + let response = await performPost(oidc.token_endpoint, '', `grant_type=refresh_token&refresh_token=${tokens.refresh_token}`, { + 'Content-type': 'application/x-www-form-urlencoded', + Authorization: 'Basic ' + applicationBase64Token(client), + }).expect(200); + tokens = response.body; + }); + + describe('tokens will be revoked', () => { + it('when user is disabled by MAPI', async () => { + await updateUserStatus(domain.id, accessToken, user.id, false); + let response = await performPost(oidc.token_endpoint, '', `grant_type=refresh_token&refresh_token=${tokens.refresh_token}`, { + 'Content-type': 'application/x-www-form-urlencoded', + Authorization: 'Basic ' + applicationBase64Token(client), + }).expect(400); + expect(response.body.error).toEqual('invalid_grant'); + expect(response.body.error_description).toEqual('Refresh token is invalid'); + }); + + it('and will remain revoked, when user is enabled back', async () => { + await updateUserStatus(domain.id, accessToken, user.id, true); + let response = await performPost(oidc.token_endpoint, '', `grant_type=refresh_token&refresh_token=${tokens.refresh_token}`, { + 'Content-type': 'application/x-www-form-urlencoded', + Authorization: 'Basic ' + applicationBase64Token(client), + }).expect(400); + expect(response.body.error).toEqual('invalid_grant'); + expect(response.body.error_description).toEqual('Refresh token is invalid'); + }); + }); +});