From 9451f09724f89a99e5287581940e9408e0a41ce9 Mon Sep 17 00:00:00 2001 From: Manoj Garai Date: Mon, 26 Feb 2024 16:21:27 +0000 Subject: [PATCH 01/22] Add ability to disable client * Add API and service methods to change client status * Add new columns for client status * Set status as active for new client * Add suspended label next to client name * Add column status_changed_by to client_details * Make client suspension details available to client owner --- .../find/DefaultFindAccountService.java | 5 +- .../account/find/FindAccountController.java | 43 +- .../api/account/find/FindAccountService.java | 6 +- .../ClientManagementAPIController.java | 13 + .../service/ClientManagementService.java | 2 + .../DefaultClientManagementService.java | 13 + .../DefaultClientRegistrationService.java | 2 + .../api/client/service/ClientConverter.java | 484 +++++----- .../iam/api/client/service/ClientService.java | 2 + .../client/service/DefaultClientService.java | 19 +- .../common/client/RegisteredClientDTO.java | 111 ++- .../client/ClientStatusChangedEvent.java | 28 + .../main/webapp/WEB-INF/tags/iamHeader.tag | 4 + .../webapp/WEB-INF/views/iam/dashboard.jsp | 2 +- .../clients/client/client.component.html | 183 ++-- .../clients/client/client.component.js | 21 +- .../status/client.status.component.html | 34 + .../client/status/client.status.component.js | 117 +++ .../clientslist/clientslist.component.html | 4 + .../clientslist/clientslist.component.js | 21 +- .../user/myclients/myclients.component.html | 13 +- .../user/myclients/myclients.component.js | 21 +- .../dashboard-app/services/clients.service.js | 11 +- .../dashboard-app/services/find.service.js | 13 +- .../main/webapp/resources/iam/css/tooltip.css | 49 + .../find/FindAccountIntegrationTests.java | 30 + .../ClientManagementAPIIntegrationTests.java | 18 + .../ClientRegistrationAPIControllerTests.java | 1 + .../client/ClientManagementServiceTests.java | 16 + ...nd_status_changed_on_to_client_details.sql | 2 + ...nd_status_changed_on_to_client_details.sql | 3 + .../db/migration/test/V100000___test_data.sql | 42 +- pom.xml | 850 +++++++++--------- 33 files changed, 1350 insertions(+), 833 deletions(-) create mode 100644 iam-login-service/src/main/java/it/infn/mw/iam/audit/events/client/ClientStatusChangedEvent.java create mode 100644 iam-login-service/src/main/webapp/resources/iam/apps/dashboard-app/components/clients/client/status/client.status.component.html create mode 100644 iam-login-service/src/main/webapp/resources/iam/apps/dashboard-app/components/clients/client/status/client.status.component.js create mode 100644 iam-login-service/src/main/webapp/resources/iam/css/tooltip.css create mode 100644 iam-persistence/src/main/resources/db/migration/h2/V103__add_active_and_status_changed_on_to_client_details.sql create mode 100644 iam-persistence/src/main/resources/db/migration/mysql/V103__add_active_and_status_changed_on_to_client_details.sql diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/api/account/find/DefaultFindAccountService.java b/iam-login-service/src/main/java/it/infn/mw/iam/api/account/find/DefaultFindAccountService.java index 3553dbeda..db350a3c5 100644 --- a/iam-login-service/src/main/java/it/infn/mw/iam/api/account/find/DefaultFindAccountService.java +++ b/iam-login-service/src/main/java/it/infn/mw/iam/api/account/find/DefaultFindAccountService.java @@ -25,7 +25,6 @@ import org.springframework.data.domain.Pageable; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Transactional; - import it.infn.mw.iam.api.scim.converter.UserConverter; import it.infn.mw.iam.api.scim.exception.IllegalArgumentException; import it.infn.mw.iam.api.scim.model.ScimListResponse; @@ -143,4 +142,8 @@ public ScimListResponse findAccountByGroupUuidWithFilter(String groupU Page results = repo.findByGroupUuidWithFilter(group.getUuid(), filter, pageable); return responseFromPage(results, converter, pageable); } + + public Optional findAccountByUuid(String uuid) { + return repo.findByUuid(uuid); + } } diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/api/account/find/FindAccountController.java b/iam-login-service/src/main/java/it/infn/mw/iam/api/account/find/FindAccountController.java index 2356b93ee..0e27d2b1e 100644 --- a/iam-login-service/src/main/java/it/infn/mw/iam/api/account/find/FindAccountController.java +++ b/iam-login-service/src/main/java/it/infn/mw/iam/api/account/find/FindAccountController.java @@ -20,20 +20,27 @@ import static java.util.Objects.isNull; import static org.springframework.web.bind.annotation.RequestMethod.GET; +import java.util.Optional; + +import javax.servlet.http.HttpServletRequest; + import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.domain.Pageable; +import org.springframework.http.HttpStatus; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.validation.BindingResult; import org.springframework.validation.annotation.Validated; -import org.springframework.web.bind.annotation.PathVariable; -import org.springframework.web.bind.annotation.RequestMapping; -import org.springframework.web.bind.annotation.RequestParam; -import org.springframework.web.bind.annotation.RestController; +import org.springframework.web.bind.annotation.*; +import com.nimbusds.jose.shaded.json.JSONObject; + +import it.infn.mw.iam.api.common.ErrorDTO; import it.infn.mw.iam.api.common.ListResponseDTO; +import it.infn.mw.iam.api.common.error.NoSuchAccountError; import it.infn.mw.iam.api.common.form.PaginatedRequestWithFilterForm; import it.infn.mw.iam.api.scim.model.ScimConstants; import it.infn.mw.iam.api.scim.model.ScimUser; +import it.infn.mw.iam.persistence.model.IamAccount; @RestController @PreAuthorize("#iam.hasScope('iam:admin.read') or #iam.hasDashboardRole('ROLE_ADMIN')") @@ -44,6 +51,7 @@ public class FindAccountController { public static final String FIND_BY_LABEL_RESOURCE = "/iam/account/find/bylabel"; public static final String FIND_BY_EMAIL_RESOURCE = "/iam/account/find/byemail"; public static final String FIND_BY_USERNAME_RESOURCE = "/iam/account/find/byusername"; + public static final String FIND_BY_UUID_RESOURCE = "/iam/account/find/byuuid/{accountUuid}"; public static final String FIND_BY_CERT_SUBJECT_RESOURCE = "/iam/account/find/bycertsubject"; public static final String FIND_BY_GROUP_RESOURCE = "/iam/account/find/bygroup/{groupUuid}"; public static final String FIND_NOT_IN_GROUP_RESOURCE = @@ -121,4 +129,31 @@ public ListResponseDTO findNotInGroup(@PathVariable String groupUuid, } } + @GetMapping(FIND_BY_UUID_RESOURCE) + @PreAuthorize("#iam.hasScope('iam:admin.read') or #iam.hasDashboardRole('ROLE_ADMIN') or hasRole('USER')") + public JSONObject findByUuid(@PathVariable String accountUuid) { + Optional iamAccount = service.findAccountByUuid(accountUuid); + if(iamAccount.isPresent()){ + return getIamAccountJson(iamAccount.get()); + } else{ + throw NoSuchAccountError.forUuid(accountUuid); + } + } + + @ResponseStatus(value = HttpStatus.NOT_FOUND) + @ExceptionHandler(NoSuchAccountError.class) + @ResponseBody + @PreAuthorize("#iam.hasScope('iam:admin.read') or #iam.hasDashboardRole('ROLE_ADMIN') or hasRole('USER')") + public ErrorDTO accountNotFoundError(HttpServletRequest req, Exception ex) { + return ErrorDTO.fromString(ex.getMessage()); + } + + private JSONObject getIamAccountJson(IamAccount iamAccount) { + JSONObject iamAccountJson = new JSONObject(); + iamAccountJson.put("id", iamAccount.getId()); + iamAccountJson.put("accountUuid", iamAccount.getUuid()); + iamAccountJson.put("username", iamAccount.getUsername()); + iamAccountJson.put("active", iamAccount.isActive()); + return iamAccountJson; + } } diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/api/account/find/FindAccountService.java b/iam-login-service/src/main/java/it/infn/mw/iam/api/account/find/FindAccountService.java index 24314cca9..aca1e154b 100644 --- a/iam-login-service/src/main/java/it/infn/mw/iam/api/account/find/FindAccountService.java +++ b/iam-login-service/src/main/java/it/infn/mw/iam/api/account/find/FindAccountService.java @@ -15,10 +15,13 @@ */ package it.infn.mw.iam.api.account.find; +import java.util.Optional; + import org.springframework.data.domain.Pageable; import it.infn.mw.iam.api.scim.model.ScimListResponse; import it.infn.mw.iam.api.scim.model.ScimUser; +import it.infn.mw.iam.persistence.model.IamAccount; public interface FindAccountService { @@ -45,5 +48,6 @@ ScimListResponse findAccountByGroupUuidWithFilter(String groupUuid, St ScimListResponse findAccountNotInGroupWithFilter(String groupUuid, String filter, Pageable pageable); - + + Optional findAccountByUuid(String uuid); } diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/api/client/management/ClientManagementAPIController.java b/iam-login-service/src/main/java/it/infn/mw/iam/api/client/management/ClientManagementAPIController.java index a761d86df..2c377ef43 100644 --- a/iam-login-service/src/main/java/it/infn/mw/iam/api/client/management/ClientManagementAPIController.java +++ b/iam-login-service/src/main/java/it/infn/mw/iam/api/client/management/ClientManagementAPIController.java @@ -33,6 +33,7 @@ import org.springframework.web.bind.annotation.DeleteMapping; import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.GetMapping; +import org.springframework.web.bind.annotation.PatchMapping; import org.springframework.web.bind.annotation.PathVariable; import org.springframework.web.bind.annotation.PostMapping; import org.springframework.web.bind.annotation.PutMapping; @@ -43,6 +44,8 @@ import org.springframework.web.bind.annotation.RestController; import com.fasterxml.jackson.annotation.JsonView; +import com.google.gson.JsonObject; +import com.google.gson.JsonParser; import it.infn.mw.iam.api.client.error.InvalidPaginationRequest; import it.infn.mw.iam.api.client.error.NoSuchClient; @@ -140,6 +143,16 @@ public RegisteredClientDTO updateClient(@PathVariable String clientId, return managementService.updateClient(clientId, client); } + @PatchMapping("/{clientId}/status") + @PreAuthorize("#iam.hasScope('iam:admin.write') or #iam.hasDashboardRole('ROLE_ADMIN')") + public void updateClientStatus(@PathVariable String clientId, + @RequestBody String body) { + JsonObject jsonObject = JsonParser.parseString(body).getAsJsonObject(); + boolean status = jsonObject.get("status").getAsBoolean(); + String userId = jsonObject.get("userId").getAsString(); + managementService.updateClientStatus(clientId, status, userId); + } + @PostMapping("/{clientId}/secret") @ResponseStatus(CREATED) @PreAuthorize("#iam.hasScope('iam:admin.write') or #iam.hasDashboardRole('ROLE_ADMIN')") diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/api/client/management/service/ClientManagementService.java b/iam-login-service/src/main/java/it/infn/mw/iam/api/client/management/service/ClientManagementService.java index 9e9531b88..ccfa02d64 100644 --- a/iam-login-service/src/main/java/it/infn/mw/iam/api/client/management/service/ClientManagementService.java +++ b/iam-login-service/src/main/java/it/infn/mw/iam/api/client/management/service/ClientManagementService.java @@ -50,6 +50,8 @@ RegisteredClientDTO updateClient(@NotBlank String clientId, void deleteClientByClientId(@NotBlank String clientId); + void updateClientStatus(String clientId, boolean status, String userId); + ListResponseDTO getClientOwners(@NotBlank String clientId, @NotNull Pageable pageable); void assignClientOwner(@NotBlank String clientId, @IamAccountId String accountId); diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/api/client/management/service/DefaultClientManagementService.java b/iam-login-service/src/main/java/it/infn/mw/iam/api/client/management/service/DefaultClientManagementService.java index 2298908e9..25c12c870 100644 --- a/iam-login-service/src/main/java/it/infn/mw/iam/api/client/management/service/DefaultClientManagementService.java +++ b/iam-login-service/src/main/java/it/infn/mw/iam/api/client/management/service/DefaultClientManagementService.java @@ -52,6 +52,7 @@ import it.infn.mw.iam.audit.events.client.ClientRegistrationAccessTokenRotatedEvent; import it.infn.mw.iam.audit.events.client.ClientRemovedEvent; import it.infn.mw.iam.audit.events.client.ClientSecretUpdatedEvent; +import it.infn.mw.iam.audit.events.client.ClientStatusChangedEvent; import it.infn.mw.iam.audit.events.client.ClientUpdatedEvent; import it.infn.mw.iam.core.IamTokenService; import it.infn.mw.iam.persistence.model.IamAccount; @@ -116,6 +117,7 @@ public RegisteredClientDTO saveNewClient(RegisteredClientDTO client) throws Pars ClientDetailsEntity entity = converter.entityFromClientManagementRequest(client); entity.setDynamicallyRegistered(false); entity.setCreatedAt(Date.from(clock.instant())); + entity.setActive(true); defaultsService.setupClientDefaults(entity); entity = clientService.saveNewClient(entity); @@ -133,6 +135,16 @@ public void deleteClientByClientId(String clientId) { eventPublisher.publishEvent(new ClientRemovedEvent(this, client)); } + @Override + public void updateClientStatus(String clientId, boolean status, String userId) { + + ClientDetailsEntity client = clientService.findClientByClientId(clientId) + .orElseThrow(ClientSuppliers.clientNotFound(clientId)); + client = clientService.updateClientStatus(client, status, userId); + String message = "Client " + (status?"enabled":"disabled"); + eventPublisher.publishEvent(new ClientStatusChangedEvent(this, client, message)); + } + @Validated(OnClientUpdate.class) @Override public RegisteredClientDTO updateClient(String clientId, RegisteredClientDTO client) @@ -148,6 +160,7 @@ public RegisteredClientDTO updateClient(String clientId, RegisteredClientDTO cli newClient.setClientId(oldClient.getClientId()); newClient.setAuthorities(oldClient.getAuthorities()); newClient.setDynamicallyRegistered(oldClient.isDynamicallyRegistered()); + newClient.setActive(oldClient.isActive()); if (NONE.equals(newClient.getTokenEndpointAuthMethod())) { newClient.setClientSecret(null); diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/api/client/registration/service/DefaultClientRegistrationService.java b/iam-login-service/src/main/java/it/infn/mw/iam/api/client/registration/service/DefaultClientRegistrationService.java index 09bc73fb8..6ef380583 100644 --- a/iam-login-service/src/main/java/it/infn/mw/iam/api/client/registration/service/DefaultClientRegistrationService.java +++ b/iam-login-service/src/main/java/it/infn/mw/iam/api/client/registration/service/DefaultClientRegistrationService.java @@ -330,6 +330,7 @@ public RegisteredClientDTO registerClient(RegisteredClientDTO request, ClientDetailsEntity client = converter.entityFromRegistrationRequest(request); defaultsService.setupClientDefaults(client); client.setDynamicallyRegistered(true); + client.setActive(true); checkAllowedGrantTypes(request, authentication); cleanupRequestedScopes(client, authentication); @@ -410,6 +411,7 @@ public RegisteredClientDTO updateClient(String clientId, RegisteredClientDTO req newClient.setAuthorities(oldClient.getAuthorities()); newClient.setCreatedAt(oldClient.getCreatedAt()); newClient.setReuseRefreshToken(oldClient.isReuseRefreshToken()); + newClient.setActive(oldClient.isActive()); ClientDetailsEntity savedClient = clientService.updateClient(newClient); diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/api/client/service/ClientConverter.java b/iam-login-service/src/main/java/it/infn/mw/iam/api/client/service/ClientConverter.java index cb251e9c4..bc5040cf6 100644 --- a/iam-login-service/src/main/java/it/infn/mw/iam/api/client/service/ClientConverter.java +++ b/iam-login-service/src/main/java/it/infn/mw/iam/api/client/service/ClientConverter.java @@ -1,243 +1,247 @@ -/** - * Copyright (c) Istituto Nazionale di Fisica Nucleare (INFN). 2016-2021 - * - * 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. - */ -package it.infn.mw.iam.api.client.service; - -import static java.util.Objects.isNull; -import static java.util.stream.Collectors.toSet; - -import java.text.ParseException; -import java.util.HashSet; -import java.util.Optional; -import java.util.Set; - -import org.mitre.oauth2.model.ClientDetailsEntity; -import org.mitre.oauth2.model.ClientDetailsEntity.AuthMethod; -import org.mitre.oauth2.model.PKCEAlgorithm; -import org.springframework.stereotype.Component; - -import com.google.common.base.Strings; -import com.nimbusds.jose.jwk.JWKSet; - -import it.infn.mw.iam.api.client.registration.ClientRegistrationApiController; -import it.infn.mw.iam.api.common.client.AuthorizationGrantType; -import it.infn.mw.iam.api.common.client.OAuthResponseType; -import it.infn.mw.iam.api.common.client.RegisteredClientDTO; -import it.infn.mw.iam.api.common.client.TokenEndpointAuthenticationMethod; -import it.infn.mw.iam.config.IamProperties; -import it.infn.mw.iam.config.client_registration.ClientRegistrationProperties; - -@Component -public class ClientConverter { - - private final IamProperties iamProperties; - - private final String clientRegistrationBaseUrl; - private final ClientRegistrationProperties clientRegistrationProperties; - - public ClientConverter(IamProperties properties, - ClientRegistrationProperties clientRegistrationProperties) { - this.iamProperties = properties; - this.clientRegistrationProperties = clientRegistrationProperties; - clientRegistrationBaseUrl = - String.format("%s%s", iamProperties.getBaseUrl(), ClientRegistrationApiController.ENDPOINT); - } - - private Set cloneSet(Set stringSet) { - Set result = new HashSet<>(); - if (stringSet != null) { - result.addAll(stringSet); - } - return result; - } - - - public ClientDetailsEntity entityFromClientManagementRequest(RegisteredClientDTO dto) - throws ParseException { - ClientDetailsEntity client = entityFromRegistrationRequest(dto); - - if (dto.getAccessTokenValiditySeconds() != null && dto.getAccessTokenValiditySeconds() > 0) { - client.setAccessTokenValiditySeconds(dto.getAccessTokenValiditySeconds()); - } - // Refresh Token validity seconds zero value is valid and means infinite duration - if (dto.getRefreshTokenValiditySeconds() != null && dto.getRefreshTokenValiditySeconds() >= 0) { - client.setRefreshTokenValiditySeconds(dto.getRefreshTokenValiditySeconds()); - } - if (dto.getIdTokenValiditySeconds() != null && dto.getIdTokenValiditySeconds() > 0) { - client.setIdTokenValiditySeconds(dto.getIdTokenValiditySeconds()); - } - if (dto.getDeviceCodeValiditySeconds() != null && dto.getDeviceCodeValiditySeconds() > 0) { - client.setDeviceCodeValiditySeconds(dto.getDeviceCodeValiditySeconds()); - } - - client.setAllowIntrospection(dto.isAllowIntrospection()); - client.setReuseRefreshToken(dto.isReuseRefreshToken()); - client.setClearAccessTokensOnRefresh(dto.isClearAccessTokensOnRefresh()); - - if (dto.getCodeChallengeMethod() != null) { - PKCEAlgorithm pkceAlgo = PKCEAlgorithm.parse(dto.getCodeChallengeMethod()); - client.setCodeChallengeMethod(pkceAlgo); - } - - if (dto.getTokenEndpointAuthMethod() != null) { - client - .setTokenEndpointAuthMethod(AuthMethod.getByValue(dto.getTokenEndpointAuthMethod().name())); - } - - client.setRequireAuthTime(Boolean.valueOf(dto.isRequireAuthTime())); - - return client; - } - - - - public RegisteredClientDTO registeredClientDtoFromEntity(ClientDetailsEntity entity) { - RegisteredClientDTO clientDTO = new RegisteredClientDTO(); - - clientDTO.setClientId(entity.getClientId()); - clientDTO.setClientSecret(entity.getClientSecret()); - clientDTO.setClientName(entity.getClientName()); - clientDTO.setContacts(entity.getContacts()); - clientDTO.setGrantTypes(entity.getGrantTypes() - .stream() - .map(AuthorizationGrantType::fromGrantType) - .collect(toSet())); - - clientDTO.setJwksUri(entity.getJwksUri()); - clientDTO.setRedirectUris(cloneSet(entity.getRedirectUris())); - - clientDTO.setTokenEndpointAuthMethod(TokenEndpointAuthenticationMethod - .valueOf(Optional.ofNullable(entity.getTokenEndpointAuthMethod()) - .orElse(AuthMethod.NONE) - .getValue())); - - clientDTO.setScope(cloneSet(entity.getScope())); - clientDTO.setTosUri(entity.getTosUri()); - +/** + * Copyright (c) Istituto Nazionale di Fisica Nucleare (INFN). 2016-2021 + * + * 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. + */ +package it.infn.mw.iam.api.client.service; + +import static java.util.Objects.isNull; +import static java.util.stream.Collectors.toSet; + +import java.text.ParseException; +import java.util.HashSet; +import java.util.Optional; +import java.util.Set; + +import org.mitre.oauth2.model.ClientDetailsEntity; +import org.mitre.oauth2.model.ClientDetailsEntity.AuthMethod; +import org.mitre.oauth2.model.PKCEAlgorithm; +import org.springframework.stereotype.Component; + +import com.google.common.base.Strings; +import com.nimbusds.jose.jwk.JWKSet; + +import it.infn.mw.iam.api.client.registration.ClientRegistrationApiController; +import it.infn.mw.iam.api.common.client.AuthorizationGrantType; +import it.infn.mw.iam.api.common.client.OAuthResponseType; +import it.infn.mw.iam.api.common.client.RegisteredClientDTO; +import it.infn.mw.iam.api.common.client.TokenEndpointAuthenticationMethod; +import it.infn.mw.iam.config.IamProperties; +import it.infn.mw.iam.config.client_registration.ClientRegistrationProperties; + +@Component +public class ClientConverter { + + private final IamProperties iamProperties; + + private final String clientRegistrationBaseUrl; + private final ClientRegistrationProperties clientRegistrationProperties; + + public ClientConverter(IamProperties properties, + ClientRegistrationProperties clientRegistrationProperties) { + this.iamProperties = properties; + this.clientRegistrationProperties = clientRegistrationProperties; + clientRegistrationBaseUrl = + String.format("%s%s", iamProperties.getBaseUrl(), ClientRegistrationApiController.ENDPOINT); + } + + private Set cloneSet(Set stringSet) { + Set result = new HashSet<>(); + if (stringSet != null) { + result.addAll(stringSet); + } + return result; + } + + + public ClientDetailsEntity entityFromClientManagementRequest(RegisteredClientDTO dto) + throws ParseException { + ClientDetailsEntity client = entityFromRegistrationRequest(dto); + + if (dto.getAccessTokenValiditySeconds() != null && dto.getAccessTokenValiditySeconds() > 0) { + client.setAccessTokenValiditySeconds(dto.getAccessTokenValiditySeconds()); + } + // Refresh Token validity seconds zero value is valid and means infinite duration + if (dto.getRefreshTokenValiditySeconds() != null && dto.getRefreshTokenValiditySeconds() >= 0) { + client.setRefreshTokenValiditySeconds(dto.getRefreshTokenValiditySeconds()); + } + if (dto.getIdTokenValiditySeconds() != null && dto.getIdTokenValiditySeconds() > 0) { + client.setIdTokenValiditySeconds(dto.getIdTokenValiditySeconds()); + } + if (dto.getDeviceCodeValiditySeconds() != null && dto.getDeviceCodeValiditySeconds() > 0) { + client.setDeviceCodeValiditySeconds(dto.getDeviceCodeValiditySeconds()); + } + + client.setAllowIntrospection(dto.isAllowIntrospection()); + client.setReuseRefreshToken(dto.isReuseRefreshToken()); + client.setClearAccessTokensOnRefresh(dto.isClearAccessTokensOnRefresh()); + + if (dto.getCodeChallengeMethod() != null) { + PKCEAlgorithm pkceAlgo = PKCEAlgorithm.parse(dto.getCodeChallengeMethod()); + client.setCodeChallengeMethod(pkceAlgo); + } + + if (dto.getTokenEndpointAuthMethod() != null) { + client + .setTokenEndpointAuthMethod(AuthMethod.getByValue(dto.getTokenEndpointAuthMethod().name())); + } + + client.setRequireAuthTime(Boolean.valueOf(dto.isRequireAuthTime())); + + return client; + } + + + + public RegisteredClientDTO registeredClientDtoFromEntity(ClientDetailsEntity entity) { + RegisteredClientDTO clientDTO = new RegisteredClientDTO(); + + clientDTO.setClientId(entity.getClientId()); + clientDTO.setClientSecret(entity.getClientSecret()); + clientDTO.setClientName(entity.getClientName()); + clientDTO.setContacts(entity.getContacts()); + clientDTO.setGrantTypes(entity.getGrantTypes() + .stream() + .map(AuthorizationGrantType::fromGrantType) + .collect(toSet())); + + clientDTO.setJwksUri(entity.getJwksUri()); + clientDTO.setRedirectUris(cloneSet(entity.getRedirectUris())); + + clientDTO.setTokenEndpointAuthMethod(TokenEndpointAuthenticationMethod + .valueOf(Optional.ofNullable(entity.getTokenEndpointAuthMethod()) + .orElse(AuthMethod.NONE) + .getValue())); + + clientDTO.setScope(cloneSet(entity.getScope())); + clientDTO.setTosUri(entity.getTosUri()); + clientDTO.setCreatedAt(entity.getCreatedAt()); if (entity.getClientLastUsed() != null) { clientDTO.setLastUsed(entity.getClientLastUsed().getLastUsed()); - } - clientDTO.setAccessTokenValiditySeconds(entity.getAccessTokenValiditySeconds()); - clientDTO.setAllowIntrospection(entity.isAllowIntrospection()); - clientDTO.setClearAccessTokensOnRefresh(entity.isClearAccessTokensOnRefresh()); - clientDTO.setClientDescription(entity.getClientDescription()); - clientDTO.setClientUri(entity.getClientUri()); - clientDTO.setDeviceCodeValiditySeconds(entity.getDeviceCodeValiditySeconds()); - clientDTO.setDynamicallyRegistered(entity.isDynamicallyRegistered()); - clientDTO.setIdTokenValiditySeconds(entity.getIdTokenValiditySeconds()); - clientDTO.setJwksUri(entity.getJwksUri()); - - Optional.ofNullable(entity.getJwks()).ifPresent(k -> clientDTO.setJwk(k.toString())); - clientDTO.setPolicyUri(entity.getPolicyUri()); - clientDTO.setRefreshTokenValiditySeconds(entity.getRefreshTokenValiditySeconds()); - - Optional.ofNullable(entity.getResponseTypes()) - .ifPresent(rts -> clientDTO - .setResponseTypes(rts.stream().map(OAuthResponseType::fromResponseType).collect(toSet()))); - - clientDTO.setReuseRefreshToken(entity.isReuseRefreshToken()); - - if (entity.isDynamicallyRegistered()) { - clientDTO.setRegistrationClientUri( - String.format("%s/%s", clientRegistrationBaseUrl, entity.getClientId())); - } - - if (entity.getCodeChallengeMethod() != null) { - clientDTO.setCodeChallengeMethod(entity.getCodeChallengeMethod().getName()); - } - - if (entity.getRequireAuthTime() != null) { - clientDTO.setRequireAuthTime(entity.getRequireAuthTime()); - } else { - clientDTO.setRequireAuthTime(false); - } - - return clientDTO; - } - - public ClientDetailsEntity entityFromRegistrationRequest(RegisteredClientDTO dto) - throws ParseException { - - ClientDetailsEntity client = new ClientDetailsEntity(); - - client.setClientId(dto.getClientId()); - client.setClientDescription(dto.getClientDescription()); - client.setClientName(dto.getClientName()); - client.setClientSecret(dto.getClientSecret()); - - client.setClientUri(dto.getClientUri()); - - if (!Strings.isNullOrEmpty(dto.getJwksUri())) { - client.setJwksUri(dto.getJwksUri()); - } else if (!Strings.isNullOrEmpty(dto.getJwk())) { - client.setJwks(JWKSet.parse(dto.getJwk())); - } - - client.setPolicyUri(dto.getPolicyUri()); - - client.setRedirectUris(cloneSet(dto.getRedirectUris())); - - client.setScope(cloneSet(dto.getScope())); - - client.setGrantTypes(new HashSet<>()); - - if (!isNull(dto.getGrantTypes())) { - client.setGrantTypes( - dto.getGrantTypes() - .stream() - .map(AuthorizationGrantType::getGrantType) - .collect(toSet())); - } - - if (dto.getScope().contains("offline_access")) { - client.getGrantTypes().add(AuthorizationGrantType.REFRESH_TOKEN.getGrantType()); - } - - if (!isNull(dto.getResponseTypes())) { - client.setResponseTypes( - dto.getResponseTypes().stream().map(OAuthResponseType::getResponseType).collect(toSet())); - } - - client.setContacts(cloneSet(dto.getContacts())); - - if (!isNull(dto.getTokenEndpointAuthMethod())) { - client - .setTokenEndpointAuthMethod(AuthMethod.getByValue(dto.getTokenEndpointAuthMethod().name())); - } - - if (dto.getCodeChallengeMethod() != null) { - PKCEAlgorithm pkceAlgo = PKCEAlgorithm.parse(dto.getCodeChallengeMethod()); - client.setCodeChallengeMethod(pkceAlgo); - } - - // bypasses MitreID default setting to zero inside client's entity - client.setAccessTokenValiditySeconds(clientRegistrationProperties.getClientDefaults().getDefaultAccessTokenValiditySeconds()); - client.setRefreshTokenValiditySeconds(clientRegistrationProperties.getClientDefaults().getDefaultRefreshTokenValiditySeconds()); - client.setIdTokenValiditySeconds(clientRegistrationProperties.getClientDefaults().getDefaultIdTokenValiditySeconds()); - client.setDeviceCodeValiditySeconds(clientRegistrationProperties.getClientDefaults().getDefaultDeviceCodeValiditySeconds()); - - return client; - } - - public RegisteredClientDTO registrationResponseFromClient(ClientDetailsEntity entity) { - RegisteredClientDTO response = registeredClientDtoFromEntity(entity); - response.setRegistrationClientUri( - String.format("%s/%s", clientRegistrationBaseUrl, entity.getClientId())); - - return response; - } - -} + } + clientDTO.setAccessTokenValiditySeconds(entity.getAccessTokenValiditySeconds()); + clientDTO.setAllowIntrospection(entity.isAllowIntrospection()); + clientDTO.setClearAccessTokensOnRefresh(entity.isClearAccessTokensOnRefresh()); + clientDTO.setClientDescription(entity.getClientDescription()); + clientDTO.setClientUri(entity.getClientUri()); + clientDTO.setDeviceCodeValiditySeconds(entity.getDeviceCodeValiditySeconds()); + clientDTO.setDynamicallyRegistered(entity.isDynamicallyRegistered()); + clientDTO.setIdTokenValiditySeconds(entity.getIdTokenValiditySeconds()); + clientDTO.setJwksUri(entity.getJwksUri()); + + Optional.ofNullable(entity.getJwks()).ifPresent(k -> clientDTO.setJwk(k.toString())); + clientDTO.setPolicyUri(entity.getPolicyUri()); + clientDTO.setRefreshTokenValiditySeconds(entity.getRefreshTokenValiditySeconds()); + + Optional.ofNullable(entity.getResponseTypes()) + .ifPresent(rts -> clientDTO + .setResponseTypes(rts.stream().map(OAuthResponseType::fromResponseType).collect(toSet()))); + + clientDTO.setReuseRefreshToken(entity.isReuseRefreshToken()); + + if (entity.isDynamicallyRegistered()) { + clientDTO.setRegistrationClientUri( + String.format("%s/%s", clientRegistrationBaseUrl, entity.getClientId())); + } + + if (entity.getCodeChallengeMethod() != null) { + clientDTO.setCodeChallengeMethod(entity.getCodeChallengeMethod().getName()); + } + + if (entity.getRequireAuthTime() != null) { + clientDTO.setRequireAuthTime(entity.getRequireAuthTime()); + } else { + clientDTO.setRequireAuthTime(false); + } + + clientDTO.setActive(entity.isActive()); + clientDTO.setStatusChangedOn(entity.getStatusChangedOn()); + clientDTO.setStatusChangedBy(entity.getStatusChangedBy()); + + return clientDTO; + } + + public ClientDetailsEntity entityFromRegistrationRequest(RegisteredClientDTO dto) + throws ParseException { + + ClientDetailsEntity client = new ClientDetailsEntity(); + + client.setClientId(dto.getClientId()); + client.setClientDescription(dto.getClientDescription()); + client.setClientName(dto.getClientName()); + client.setClientSecret(dto.getClientSecret()); + + client.setClientUri(dto.getClientUri()); + + if (!Strings.isNullOrEmpty(dto.getJwksUri())) { + client.setJwksUri(dto.getJwksUri()); + } else if (!Strings.isNullOrEmpty(dto.getJwk())) { + client.setJwks(JWKSet.parse(dto.getJwk())); + } + + client.setPolicyUri(dto.getPolicyUri()); + + client.setRedirectUris(cloneSet(dto.getRedirectUris())); + + client.setScope(cloneSet(dto.getScope())); + + client.setGrantTypes(new HashSet<>()); + + if (!isNull(dto.getGrantTypes())) { + client.setGrantTypes( + dto.getGrantTypes() + .stream() + .map(AuthorizationGrantType::getGrantType) + .collect(toSet())); + } + + if (dto.getScope().contains("offline_access")) { + client.getGrantTypes().add(AuthorizationGrantType.REFRESH_TOKEN.getGrantType()); + } + + if (!isNull(dto.getResponseTypes())) { + client.setResponseTypes( + dto.getResponseTypes().stream().map(OAuthResponseType::getResponseType).collect(toSet())); + } + + client.setContacts(cloneSet(dto.getContacts())); + + if (!isNull(dto.getTokenEndpointAuthMethod())) { + client + .setTokenEndpointAuthMethod(AuthMethod.getByValue(dto.getTokenEndpointAuthMethod().name())); + } + + if (dto.getCodeChallengeMethod() != null) { + PKCEAlgorithm pkceAlgo = PKCEAlgorithm.parse(dto.getCodeChallengeMethod()); + client.setCodeChallengeMethod(pkceAlgo); + } + + // bypasses MitreID default setting to zero inside client's entity + client.setAccessTokenValiditySeconds(clientRegistrationProperties.getClientDefaults().getDefaultAccessTokenValiditySeconds()); + client.setRefreshTokenValiditySeconds(clientRegistrationProperties.getClientDefaults().getDefaultRefreshTokenValiditySeconds()); + client.setIdTokenValiditySeconds(clientRegistrationProperties.getClientDefaults().getDefaultIdTokenValiditySeconds()); + client.setDeviceCodeValiditySeconds(clientRegistrationProperties.getClientDefaults().getDefaultDeviceCodeValiditySeconds()); + + return client; + } + + public RegisteredClientDTO registrationResponseFromClient(ClientDetailsEntity entity) { + RegisteredClientDTO response = registeredClientDtoFromEntity(entity); + response.setRegistrationClientUri( + String.format("%s/%s", clientRegistrationBaseUrl, entity.getClientId())); + + return response; + } + +} \ No newline at end of file diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/api/client/service/ClientService.java b/iam-login-service/src/main/java/it/infn/mw/iam/api/client/service/ClientService.java index 421439303..2c7accb0d 100644 --- a/iam-login-service/src/main/java/it/infn/mw/iam/api/client/service/ClientService.java +++ b/iam-login-service/src/main/java/it/infn/mw/iam/api/client/service/ClientService.java @@ -45,5 +45,7 @@ Optional findClientByClientIdAndAccount(String clientId, ClientDetailsEntity updateClient(ClientDetailsEntity client); + ClientDetailsEntity updateClientStatus(ClientDetailsEntity client, boolean status, String userId); + void deleteClient(ClientDetailsEntity client); } diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/api/client/service/DefaultClientService.java b/iam-login-service/src/main/java/it/infn/mw/iam/api/client/service/DefaultClientService.java index 48de8fd4c..1f6383cb0 100644 --- a/iam-login-service/src/main/java/it/infn/mw/iam/api/client/service/DefaultClientService.java +++ b/iam-login-service/src/main/java/it/infn/mw/iam/api/client/service/DefaultClientService.java @@ -88,7 +88,7 @@ private Supplier newAccountClient(IamAccount owner, @Override public ClientDetailsEntity linkClientToAccount(ClientDetailsEntity client, IamAccount owner) { IamAccountClient ac = accountClientRepo.findByAccountAndClient(owner, client) - .orElseGet(newAccountClient(owner, client)); + .orElseGet(newAccountClient(owner, client)); return ac.getClient(); } @@ -107,6 +107,13 @@ public ClientDetailsEntity updateClient(ClientDetailsEntity client) { return clientRepo.save(client); } + @Override + public ClientDetailsEntity updateClientStatus(ClientDetailsEntity client, boolean status, String userId) { + client.setActive(status); + client.setStatusChangedBy(userId); + client.setStatusChangedOn(Date.from(clock.instant())); + return clientRepo.save(client); + } @Override public Optional findClientByClientId(String clientId) { @@ -122,7 +129,7 @@ public Optional findClientByClientIdAndAccount(String clien if (maybeClient.isPresent()) { return accountClientRepo.findByAccountAndClientId(account, maybeClient.get().getId()) - .map(IamAccountClient::getClient); + .map(IamAccountClient::getClient); } return Optional.empty(); @@ -144,12 +151,12 @@ private boolean isValidAccessToken(OAuth2AccessTokenEntity a) { private void deleteTokensByClient(ClientDetailsEntity client) { // delete all valid access tokens (exclude registration and resource tokens) tokenService.getAccessTokensForClient(client) - .stream() - .filter(this::isValidAccessToken) - .forEach(at -> tokenService.revokeAccessToken(at)); + .stream() + .filter(this::isValidAccessToken) + .forEach(at -> tokenService.revokeAccessToken(at)); // delete all valid refresh tokens tokenService.getRefreshTokensForClient(client) - .forEach(rt -> tokenService.revokeRefreshToken(rt)); + .forEach(rt -> tokenService.revokeRefreshToken(rt)); } @Override diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/api/common/client/RegisteredClientDTO.java b/iam-login-service/src/main/java/it/infn/mw/iam/api/common/client/RegisteredClientDTO.java index 2125fa4d5..79c267a0f 100644 --- a/iam-login-service/src/main/java/it/infn/mw/iam/api/common/client/RegisteredClientDTO.java +++ b/iam-login-service/src/main/java/it/infn/mw/iam/api/common/client/RegisteredClientDTO.java @@ -1,21 +1,21 @@ -/** - * Copyright (c) Istituto Nazionale di Fisica Nucleare (INFN). 2016-2021 - * - * 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. - */ +/** + * Copyright (c) Istituto Nazionale di Fisica Nucleare (INFN). 2016-2021 + * + * 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. + */ package it.infn.mw.iam.api.common.client; -import java.time.LocalDate; +import java.time.LocalDate; import java.util.Date; import java.util.Set; @@ -28,9 +28,9 @@ import javax.validation.constraints.Pattern; import javax.validation.constraints.Size; -import org.hibernate.validator.constraints.URL; - -import com.fasterxml.jackson.annotation.JsonFormat; +import org.hibernate.validator.constraints.URL; + +import com.fasterxml.jackson.annotation.JsonFormat; import com.fasterxml.jackson.annotation.JsonInclude; import com.fasterxml.jackson.annotation.JsonView; import com.fasterxml.jackson.databind.PropertyNamingStrategies; @@ -50,6 +50,7 @@ import it.infn.mw.iam.api.client.registration.validation.ValidTokenEndpointAuthMethod; import it.infn.mw.iam.api.common.ClientViews; + @JsonNaming(PropertyNamingStrategies.SnakeCaseStrategy.class) @JsonInclude(JsonInclude.Include.NON_EMPTY) @ValidGrantType(groups = {OnClientCreation.class, OnClientUpdate.class, @@ -83,7 +84,8 @@ public class RegisteredClientDTO { private String clientSecret; @Size(min = 4, max = 256, - groups = {OnDynamicClientRegistration.class, OnClientCreation.class, OnClientUpdate.class, OnDynamicClientUpdate.class}, + groups = {OnDynamicClientRegistration.class, OnClientCreation.class, OnClientUpdate.class, + OnDynamicClientUpdate.class}, message = "Invalid length: must be between 4 and 256 characters") @NotBlank(groups = {OnDynamicClientRegistration.class, OnClientCreation.class}, message = "should not be blank") @@ -161,20 +163,20 @@ public class RegisteredClientDTO { ClientViews.DynamicRegistration.class}) private TokenEndpointAuthenticationMethod tokenEndpointAuthMethod; - @Valid - @Size(max = 512, - groups = {OnDynamicClientRegistration.class, OnDynamicClientUpdate.class, - OnClientCreation.class, OnClientUpdate.class}) - @JsonSerialize(using = CollectionAsStringSerializer.class) - @JsonDeserialize(using = StringAsSetOfStringsDeserializer.class) - @JsonView({ClientViews.Limited.class, ClientViews.Full.class, ClientViews.ClientManagement.class, - ClientViews.DynamicRegistration.class}) - private Set<@NotBlank(groups = {OnDynamicClientRegistration.class, OnDynamicClientUpdate.class, - OnClientCreation.class, OnClientUpdate.class}, - message = "must not include blank strings") @Size(min = 1, max = 2048, - message = "string size must be between 1 and 2048", - groups = {OnDynamicClientRegistration.class, OnDynamicClientUpdate.class, - OnClientCreation.class, OnClientUpdate.class}) String> scope = + @Valid + @Size(max = 512, + groups = {OnDynamicClientRegistration.class, OnDynamicClientUpdate.class, + OnClientCreation.class, OnClientUpdate.class}) + @JsonSerialize(using = CollectionAsStringSerializer.class) + @JsonDeserialize(using = StringAsSetOfStringsDeserializer.class) + @JsonView({ClientViews.Limited.class, ClientViews.Full.class, ClientViews.ClientManagement.class, + ClientViews.DynamicRegistration.class}) + private Set<@NotBlank(groups = {OnDynamicClientRegistration.class, OnDynamicClientUpdate.class, + OnClientCreation.class, OnClientUpdate.class}, + message = "must not include blank strings") @Size(min = 1, max = 2048, + message = "string size must be between 1 and 2048", + groups = {OnDynamicClientRegistration.class, OnDynamicClientUpdate.class, + OnClientCreation.class, OnClientUpdate.class}) String> scope = Sets.newHashSet(); @Min(value = 0, groups = OnClientCreation.class) @@ -250,10 +252,22 @@ public class RegisteredClientDTO { ClientViews.DynamicRegistration.class}) @Pattern(regexp = "^$|none|plain|S256", message = "must be either an empty string, none, plain or S256", - groups = {OnClientCreation.class, - OnClientUpdate.class, OnDynamicClientRegistration.class, OnDynamicClientUpdate.class}) + groups = {OnClientCreation.class, OnClientUpdate.class, OnDynamicClientRegistration.class, + OnDynamicClientUpdate.class}) private String codeChallengeMethod; + @JsonView({ClientViews.Full.class, ClientViews.ClientManagement.class, + ClientViews.DynamicRegistration.class}) + private boolean active; + + @JsonView({ClientViews.Full.class, ClientViews.ClientManagement.class, + ClientViews.DynamicRegistration.class}) + private Date statusChangedOn; + + @JsonView({ClientViews.Limited.class, ClientViews.Full.class, ClientViews.ClientManagement.class, + ClientViews.DynamicRegistration.class}) + private String statusChangedBy; + public String getClientId() { return clientId; } @@ -511,4 +525,27 @@ public void setDefaultMaxAge(Integer defaultMaxAge) { this.defaultMaxAge = defaultMaxAge; } -} + public boolean isActive() { + return active; + } + + public void setActive(boolean active) { + this.active = active; + } + + public Date getStatusChangedOn() { + return statusChangedOn; + } + + public void setStatusChangedOn(Date statusChangedOn) { + this.statusChangedOn = statusChangedOn; + } + + public void setStatusChangedBy(String statusChangedBy) { + this.statusChangedBy = statusChangedBy; + } + + public String getStatusChangedBy() { + return statusChangedBy; + } +} \ No newline at end of file diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/audit/events/client/ClientStatusChangedEvent.java b/iam-login-service/src/main/java/it/infn/mw/iam/audit/events/client/ClientStatusChangedEvent.java new file mode 100644 index 000000000..c48243d50 --- /dev/null +++ b/iam-login-service/src/main/java/it/infn/mw/iam/audit/events/client/ClientStatusChangedEvent.java @@ -0,0 +1,28 @@ +/** + * Copyright (c) Istituto Nazionale di Fisica Nucleare (INFN). 2016-2021 + * + * 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. + */ +package it.infn.mw.iam.audit.events.client; + +import org.mitre.oauth2.model.ClientDetailsEntity; + +public class ClientStatusChangedEvent extends ClientEvent { + + private static final long serialVersionUID = 1L; + + public ClientStatusChangedEvent(Object source, ClientDetailsEntity client, String message) { + super(source, client, message); + } + +} diff --git a/iam-login-service/src/main/webapp/WEB-INF/tags/iamHeader.tag b/iam-login-service/src/main/webapp/WEB-INF/tags/iamHeader.tag index 61b29fce3..076c9dfd6 100644 --- a/iam-login-service/src/main/webapp/WEB-INF/tags/iamHeader.tag +++ b/iam-login-service/src/main/webapp/WEB-INF/tags/iamHeader.tag @@ -43,6 +43,10 @@ rel="stylesheet" href="${resourcesPrefix}/iam/css/iam.css"> + + - + diff --git a/iam-login-service/src/main/webapp/resources/iam/apps/dashboard-app/components/clients/client/client.component.html b/iam-login-service/src/main/webapp/resources/iam/apps/dashboard-app/components/clients/client/client.component.html index 84bd1a220..c03f21c5c 100644 --- a/iam-login-service/src/main/webapp/resources/iam/apps/dashboard-app/components/clients/client/client.component.html +++ b/iam-login-service/src/main/webapp/resources/iam/apps/dashboard-app/components/clients/client/client.component.html @@ -1,90 +1,95 @@ - -
-
-

-    {{$ctrl.clientVal.client_name}} - Create a new client -

- -
- -
-
-
-
- - - Main - - - - - Credentials - - - - - - Scopes - - - - - Grant types - - - - - Tokens - - - - - Crypto - - - - - Other info - - - - - Owners - - - -
-
- - - - -
-
-
-
- - + +
+
+

+    {{$ctrl.clientVal.client_name}} + Create a new client +

+
Suspended + {{$ctrl.clientStatusMessage}} +
+ +
+ +
+
+
+
+ + + Main + + + + + Credentials + + + + + + Scopes + + + + + Grant types + + + + + Tokens + + + + + Crypto + + + + + Other info + + + + + Owners + + + +
+
+ + + + + +
+
+
+
+ +
\ No newline at end of file diff --git a/iam-login-service/src/main/webapp/resources/iam/apps/dashboard-app/components/clients/client/client.component.js b/iam-login-service/src/main/webapp/resources/iam/apps/dashboard-app/components/clients/client/client.component.js index 468092385..2084a890c 100644 --- a/iam-login-service/src/main/webapp/resources/iam/apps/dashboard-app/components/clients/client/client.component.js +++ b/iam-login-service/src/main/webapp/resources/iam/apps/dashboard-app/components/clients/client/client.component.js @@ -17,7 +17,7 @@ 'use strict'; - function ClientController(ClientsService, toaster, $uibModal, $location) { + function ClientController(ClientsService, FindService, toaster, $uibModal, $location) { var self = this; self.resetVal = resetVal; @@ -25,6 +25,7 @@ self.loadClient = loadClient; self.deleteClient = deleteClient; self.cancel = cancel; + self.getClientStatusMessage = getClientStatusMessage; self.$onInit = function () { if (self.newClient) { @@ -131,6 +132,22 @@ } }); } + + function getClientStatusMessage(){ + FindService.findAccountByUuid(self.clientVal.status_changed_by).then(function(res){ + self.clientStatusMessage = "Suspended by " + res.username + " on " + getFormatedDate(self.clientVal.status_changed_on); + }).catch(function (res) { + console.debug("Error retrieving user account!", res); + }); + } + + function getFormatedDate(dateToFormat){ + var dateISOString = new Date(dateToFormat).toISOString(); + var ymd = dateISOString.split('T')[0]; + //Remove milliseconds + var time = dateISOString.split('T')[1].slice(0, -5); + return ymd + " " + time; + } } angular @@ -147,7 +164,7 @@ newClient: '<', clientOwners: '<' }, - controller: ['ClientsService', 'toaster', '$uibModal', '$location', ClientController], + controller: ['ClientsService', 'FindService', 'toaster', '$uibModal', '$location', ClientController], controllerAs: '$ctrl' }; } diff --git a/iam-login-service/src/main/webapp/resources/iam/apps/dashboard-app/components/clients/client/status/client.status.component.html b/iam-login-service/src/main/webapp/resources/iam/apps/dashboard-app/components/clients/client/status/client.status.component.html new file mode 100644 index 000000000..f74d56e6d --- /dev/null +++ b/iam-login-service/src/main/webapp/resources/iam/apps/dashboard-app/components/clients/client/status/client.status.component.html @@ -0,0 +1,34 @@ + + + + + + \ No newline at end of file diff --git a/iam-login-service/src/main/webapp/resources/iam/apps/dashboard-app/components/clients/client/status/client.status.component.js b/iam-login-service/src/main/webapp/resources/iam/apps/dashboard-app/components/clients/client/status/client.status.component.js new file mode 100644 index 000000000..6cb82457f --- /dev/null +++ b/iam-login-service/src/main/webapp/resources/iam/apps/dashboard-app/components/clients/client/status/client.status.component.js @@ -0,0 +1,117 @@ +/* + * Copyright (c) Istituto Nazionale di Fisica Nucleare (INFN). 2016-2021 + * + * 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. + */ +(function() { + 'use strict'; + + function ClientStatusController(toaster, ModalService, ClientsService) { + var self = this; + + self.$onInit = function() { + self.enabled = true; + }; + + self.handleError = function(error) { + console.error(error); + self.enabled = true; + }; + + self.handleSuccess = function() { + self.enabled = true; + ClientsService.retrieveClient(self.client.client_id).then(function (client) { + console.debug("Loaded client", client); + self.client = client; + self.clientVal = angular.copy(self.client); + if (client.active) { + toaster.pop({ + type: 'success', + body: + `Client '${client.client_name}' has been restored successfully.` + }); + } else { + toaster.pop({ + type: 'success', + body: `Client '${client.client_name}' is now disabled.` + }); + } + }).catch(function (res) { + console.debug("Error retrieving client!", res); + toaster.pop({ + type: 'error', + body: 'Error retrieving client!' + }); + }); + }; + + self.enableClient = function() { + return ClientsService.setClientActiveStatus(self.client.client_id, true, self.loggedUserId) + .then(self.handleSuccess) + .catch(self.handleError); + }; + + self.disableClient = function() { + return ClientsService.setClientActiveStatus(self.client.client_id, false, self.loggedUserId) + .then(self.handleSuccess) + .catch(self.handleError); + }; + + + self.openDialog = function(loggedUserId) { + + var modalOptions = null; + var updateStatusFunc = null; + self.loggedUserId = loggedUserId; + + if (self.client.active) { + modalOptions = { + closeButtonText: 'Cancel', + actionButtonText: 'Disable client', + headerText: 'Disable ' + self.client.client_name, + bodyText: + `Are you sure you want to disable client '${self.client.client_name}'?` + }; + updateStatusFunc = self.disableClient; + + } else { + modalOptions = { + closeButtonText: 'Cancel', + actionButtonText: 'Restore client', + headerText: 'Restore ' + self.client.client_name, + bodyText: + `Are you sure you want to restore client '${self.client.client_name}'?` + }; + updateStatusFunc = self.enableClient; + } + + self.enable = false; + ModalService.showModal({}, modalOptions) + .then(function() { updateStatusFunc(); }) + .catch(function() { + console.debug("Error updating client status!", res); + }); + }; + } + + angular.module('dashboardApp').component('clientStatus', { + require: {clientCtrl: '^client'}, + bindings: {client: '='}, + templateUrl: + '/resources/iam/apps/dashboard-app/components/clients/client/status/client.status.component.html', + controller: [ + 'toaster', 'ModalService', 'ClientsService', ClientStatusController + ] + }); + +})(); \ No newline at end of file diff --git a/iam-login-service/src/main/webapp/resources/iam/apps/dashboard-app/components/clients/clientslist/clientslist.component.html b/iam-login-service/src/main/webapp/resources/iam/apps/dashboard-app/components/clients/clientslist/clientslist.component.html index 83cdb6218..fb7426469 100644 --- a/iam-login-service/src/main/webapp/resources/iam/apps/dashboard-app/components/clients/clientslist/clientslist.component.html +++ b/iam-login-service/src/main/webapp/resources/iam/apps/dashboard-app/components/clients/clientslist/clientslist.component.html @@ -92,6 +92,10 @@
{{c.client_id}}
+
Suspended + {{$ctrl.clientStatusMessage}} +
diff --git a/iam-login-service/src/main/webapp/resources/iam/apps/dashboard-app/components/clients/clientslist/clientslist.component.js b/iam-login-service/src/main/webapp/resources/iam/apps/dashboard-app/components/clients/clientslist/clientslist.component.js index bf893ccb2..50ad86daf 100644 --- a/iam-login-service/src/main/webapp/resources/iam/apps/dashboard-app/components/clients/clientslist/clientslist.component.js +++ b/iam-login-service/src/main/webapp/resources/iam/apps/dashboard-app/components/clients/clientslist/clientslist.component.js @@ -16,7 +16,7 @@ (function () { 'use strict'; - function ClientsListController($filter, $uibModal, ClientsService, toaster) { + function ClientsListController($filter, $uibModal, ClientsService, FindService, toaster) { var self = this; self.searchFilter = ''; @@ -27,6 +27,7 @@ self.onChangePage = onChangePage; self.deleteClient = deleteClient; self.clientTrackLastUsed = getClientTrackLastUsed(); + self.getClientStatusMessage = getClientStatusMessage; self.$onInit = function () { console.debug('ClientsListController.self', self); @@ -118,6 +119,22 @@ } }); } + + function getClientStatusMessage(client){ + FindService.findAccountByUuid(client.status_changed_by).then(function(res){ + self.clientStatusMessage = "Suspended by " + res.username + " on " + getFormatedDate(client.status_changed_on); + }).catch(function (res) { + console.debug("Error retrieving user account!", res); + }); + } + + function getFormatedDate(dateToFormat){ + var dateISOString = new Date(dateToFormat).toISOString(); + var ymd = dateISOString.split('T')[0]; + //Remove milliseconds + var time = dateISOString.split('T')[1].slice(0, -5); + return ymd + " " + time; + } } angular @@ -130,7 +147,7 @@ bindings: { clients: "<" }, - controller: ['$filter', '$uibModal', 'ClientsService', 'toaster', ClientsListController], + controller: ['$filter', '$uibModal', 'ClientsService', 'FindService', 'toaster', ClientsListController], controllerAs: '$ctrl' }; } diff --git a/iam-login-service/src/main/webapp/resources/iam/apps/dashboard-app/components/user/myclients/myclients.component.html b/iam-login-service/src/main/webapp/resources/iam/apps/dashboard-app/components/user/myclients/myclients.component.html index 7bb427ae7..e2c89b4cf 100644 --- a/iam-login-service/src/main/webapp/resources/iam/apps/dashboard-app/components/user/myclients/myclients.component.html +++ b/iam-login-service/src/main/webapp/resources/iam/apps/dashboard-app/components/user/myclients/myclients.component.html @@ -48,19 +48,28 @@

ng-repeat="client in $ctrl.clients.Resources">
- {{client.client_name}} + {{client.client_name}}
{{client.client_id}}
+
Suspended + {{$ctrl.clientStatusMessage}} +
- +
From a9ea66207ce15d345dd59176d9e1f07c8403d12d Mon Sep 17 00:00:00 2001 From: Manoj Garai Date: Wed, 24 Apr 2024 16:15:29 +0100 Subject: [PATCH 06/22] Add AUP re-sign modal and controller --- .../components/user/user.component.js | 38 +++++++++++++++++ .../templates/home/resignAup.html | 41 +++++++++++++++++++ 2 files changed, 79 insertions(+) create mode 100644 iam-login-service/src/main/webapp/resources/iam/apps/dashboard-app/templates/home/resignAup.html diff --git a/iam-login-service/src/main/webapp/resources/iam/apps/dashboard-app/components/user/user.component.js b/iam-login-service/src/main/webapp/resources/iam/apps/dashboard-app/components/user/user.component.js index 6aa8ea1fd..e59fc4b4a 100644 --- a/iam-login-service/src/main/webapp/resources/iam/apps/dashboard-app/components/user/user.component.js +++ b/iam-login-service/src/main/webapp/resources/iam/apps/dashboard-app/components/user/user.component.js @@ -16,6 +16,29 @@ (function () { 'use strict'; + function ResignAupController($scope, $uibModalInstance, AupService, user) { + var self = this; + self.enabled = true; + self.user = user; + + self.cancel = function() { + $uibModalInstance.close(); + }; + + self.submit = function() { + self.error = undefined; + self.enabled = false; + AupService.signAup() + .then(function(res) { + $uibModalInstance.close('AUP signature re-signed succesfully'); + self.enabled = true; + }).catch(function(res) { + self.error = res.data.error; + self.enabled = true; + }); + }; + } + function UserController( $timeout, $cookies, $rootScope, $uibModal, ModalService, scimFactory, UserService, Utils, toaster) { @@ -116,6 +139,21 @@ }) .catch(self.handleError); }; + + self.openSignAUPModal = function() { + var modalInstance = $uibModal.open({ + templateUrl: '/resources/iam/apps/dashboard-app/templates/home/resignAup.html', + controller: ResignAupController, + controllerAs: '$ctrl', + resolve: {user: function() { return self.user; }} + }); + + modalInstance.result.then(function(msg) { + toaster.pop({type: 'success', body: msg}); + }, function () { + console.log('Re-sign AUP modal dismissed at: ' + new Date()); + }); + }; } diff --git a/iam-login-service/src/main/webapp/resources/iam/apps/dashboard-app/templates/home/resignAup.html b/iam-login-service/src/main/webapp/resources/iam/apps/dashboard-app/templates/home/resignAup.html new file mode 100644 index 000000000..d5414eb95 --- /dev/null +++ b/iam-login-service/src/main/webapp/resources/iam/apps/dashboard-app/templates/home/resignAup.html @@ -0,0 +1,41 @@ + + + From 615d5481f94ea89488923b23dcbe2a4108388a5d Mon Sep 17 00:00:00 2001 From: Manoj Garai Date: Wed, 24 Apr 2024 16:16:22 +0100 Subject: [PATCH 07/22] Add sign AUP function --- .../iam/apps/dashboard-app/services/aup.service.js | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/iam-login-service/src/main/webapp/resources/iam/apps/dashboard-app/services/aup.service.js b/iam-login-service/src/main/webapp/resources/iam/apps/dashboard-app/services/aup.service.js index e5c2d163c..24b7cb2d5 100644 --- a/iam-login-service/src/main/webapp/resources/iam/apps/dashboard-app/services/aup.service.js +++ b/iam-login-service/src/main/webapp/resources/iam/apps/dashboard-app/services/aup.service.js @@ -30,7 +30,8 @@ touchAup: touchAup, deleteAup: deleteAup, getAupSignature: getAupSignature, - getAupSignatureForUser: getAupSignatureForUser + getAupSignatureForUser: getAupSignatureForUser, + signAup: signAup }; return service; @@ -80,5 +81,9 @@ return $q.reject(res); }); } + + function signAup() { + return $http.post('/iam/aup/sign'); + } } })(); \ No newline at end of file From 5672a3da57e0c42fa686421aeb81c41423aa7216 Mon Sep 17 00:00:00 2001 From: Manoj Garai Date: Fri, 26 Apr 2024 15:46:13 +0100 Subject: [PATCH 08/22] Add message on cancelation --- .../iam/apps/dashboard-app/components/user/user.component.js | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/iam-login-service/src/main/webapp/resources/iam/apps/dashboard-app/components/user/user.component.js b/iam-login-service/src/main/webapp/resources/iam/apps/dashboard-app/components/user/user.component.js index e59fc4b4a..923ee2a0b 100644 --- a/iam-login-service/src/main/webapp/resources/iam/apps/dashboard-app/components/user/user.component.js +++ b/iam-login-service/src/main/webapp/resources/iam/apps/dashboard-app/components/user/user.component.js @@ -22,7 +22,7 @@ self.user = user; self.cancel = function() { - $uibModalInstance.close(); + $uibModalInstance.close('Cancelled'); }; self.submit = function() { From 4ab275a23060e8093e54b49010c3275d26870dcd Mon Sep 17 00:00:00 2001 From: Manoj Garai Date: Mon, 29 Apr 2024 16:27:06 +0100 Subject: [PATCH 09/22] Add new event for re-sign AUP --- .../audit/events/aup/AupResignedEvent.java | 47 +++++++++++++++++++ 1 file changed, 47 insertions(+) create mode 100644 iam-login-service/src/main/java/it/infn/mw/iam/audit/events/aup/AupResignedEvent.java diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/audit/events/aup/AupResignedEvent.java b/iam-login-service/src/main/java/it/infn/mw/iam/audit/events/aup/AupResignedEvent.java new file mode 100644 index 000000000..bc1049726 --- /dev/null +++ b/iam-login-service/src/main/java/it/infn/mw/iam/audit/events/aup/AupResignedEvent.java @@ -0,0 +1,47 @@ +/** + * Copyright (c) Istituto Nazionale di Fisica Nucleare (INFN). 2016-2021 + * + * 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. + */ +package it.infn.mw.iam.audit.events.aup; + +import static java.lang.String.format; + +import com.fasterxml.jackson.databind.annotation.JsonSerialize; + +import it.infn.mw.iam.audit.events.IamAuditApplicationEvent; +import it.infn.mw.iam.audit.utils.IamAupSignatureSerializer; +import it.infn.mw.iam.persistence.model.IamAupSignature; + +public class AupResignedEvent extends IamAuditApplicationEvent { + + /** + * + */ + private static final long serialVersionUID = 1L; + + @JsonSerialize(using=IamAupSignatureSerializer.class) + final IamAupSignature signature; + + public AupResignedEvent(Object source, IamAupSignature signature) { + super(IamEventCategory.AUP, source, format("User %s re-signed the AUP", + signature.getAccount().getUsername())); + this.signature = signature; + + } + + public IamAupSignature getSignature() { + return signature; + } + +} From 20f30bc167a66437785e12240fa44b9dbfaa5776 Mon Sep 17 00:00:00 2001 From: Manoj Garai Date: Mon, 29 Apr 2024 16:29:34 +0100 Subject: [PATCH 10/22] Use update signature method to update sign time --- .../api/aup/AupSignaturePageController.java | 35 +++++++++++++++++++ .../components/user/user.component.js | 7 ++-- .../dashboard-app/services/aup.service.js | 6 ++-- 3 files changed, 42 insertions(+), 6 deletions(-) diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/api/aup/AupSignaturePageController.java b/iam-login-service/src/main/java/it/infn/mw/iam/api/aup/AupSignaturePageController.java index c3c93079a..900d5243e 100644 --- a/iam-login-service/src/main/java/it/infn/mw/iam/api/aup/AupSignaturePageController.java +++ b/iam-login-service/src/main/java/it/infn/mw/iam/api/aup/AupSignaturePageController.java @@ -29,20 +29,27 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.ApplicationEventPublisher; +import org.springframework.http.HttpStatus; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.security.web.savedrequest.SavedRequest; import org.springframework.stereotype.Controller; +import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; +import org.springframework.web.bind.annotation.ResponseBody; +import org.springframework.web.bind.annotation.ResponseStatus; import org.springframework.web.servlet.ModelAndView; import it.infn.mw.iam.api.account.AccountUtils; +import it.infn.mw.iam.api.common.ErrorDTO; +import it.infn.mw.iam.audit.events.aup.AupResignedEvent; import it.infn.mw.iam.audit.events.aup.AupSignedEvent; import it.infn.mw.iam.core.time.TimeProvider; import it.infn.mw.iam.persistence.model.IamAccount; import it.infn.mw.iam.persistence.model.IamAup; import it.infn.mw.iam.persistence.model.IamAupSignature; import it.infn.mw.iam.persistence.repository.IamAupRepository; +import it.infn.mw.iam.persistence.repository.IamAupSignatureNotFoundError; import it.infn.mw.iam.persistence.repository.IamAupSignatureRepository; @Controller @@ -128,6 +135,34 @@ public ModelAndView signAup(HttpServletRequest request, HttpServletResponse resp return new ModelAndView("redirect:/dashboard"); } + + @PreAuthorize("hasRole('USER')") + @RequestMapping(method = RequestMethod.PATCH, value = "/iam/aup/sign") + public void resignAup(HttpServletRequest request, HttpServletResponse response) { + + Optional aup = repo.findDefaultAup(); + IamAccount account = null; + + if (aup.isPresent()) { + Date now = new Date(timeProvider.currentTimeMillis()); + account = accountUtils.getAuthenticatedUserAccount().orElseThrow( + () -> new IllegalStateException("No iam account found for authenticated user")); + + IamAupSignature signature = signatureRepo.updateSignatureForAccount(account, now); + + publisher.publishEvent(new AupResignedEvent(this, signature)); + } else { + throw new IamAupSignatureNotFoundError(account); + } + } + + @ResponseStatus(value = HttpStatus.NOT_FOUND) + @ExceptionHandler(IamAupSignatureNotFoundError.class) + @ResponseBody + @PreAuthorize("hasRole('USER')") + public ErrorDTO accountNotFoundError(HttpServletRequest req, Exception ex) { + return ErrorDTO.fromString(ex.getMessage()); + } } diff --git a/iam-login-service/src/main/webapp/resources/iam/apps/dashboard-app/components/user/user.component.js b/iam-login-service/src/main/webapp/resources/iam/apps/dashboard-app/components/user/user.component.js index 923ee2a0b..a8724af99 100644 --- a/iam-login-service/src/main/webapp/resources/iam/apps/dashboard-app/components/user/user.component.js +++ b/iam-login-service/src/main/webapp/resources/iam/apps/dashboard-app/components/user/user.component.js @@ -16,7 +16,7 @@ (function () { 'use strict'; - function ResignAupController($scope, $uibModalInstance, AupService, user) { + function ResignAupController($scope, $uibModalInstance, AupService, user, toaster) { var self = this; self.enabled = true; self.user = user; @@ -28,13 +28,14 @@ self.submit = function() { self.error = undefined; self.enabled = false; - AupService.signAup() + AupService.resignAup() .then(function(res) { $uibModalInstance.close('AUP signature re-signed succesfully'); self.enabled = true; - }).catch(function(res) { + }, function(res) { self.error = res.data.error; self.enabled = true; + toaster.pop({ type: 'error', body: self.error}); }); }; } diff --git a/iam-login-service/src/main/webapp/resources/iam/apps/dashboard-app/services/aup.service.js b/iam-login-service/src/main/webapp/resources/iam/apps/dashboard-app/services/aup.service.js index 24b7cb2d5..59ebdcd45 100644 --- a/iam-login-service/src/main/webapp/resources/iam/apps/dashboard-app/services/aup.service.js +++ b/iam-login-service/src/main/webapp/resources/iam/apps/dashboard-app/services/aup.service.js @@ -31,7 +31,7 @@ deleteAup: deleteAup, getAupSignature: getAupSignature, getAupSignatureForUser: getAupSignatureForUser, - signAup: signAup + resignAup: resignAup }; return service; @@ -82,8 +82,8 @@ }); } - function signAup() { - return $http.post('/iam/aup/sign'); + function resignAup() { + return $http.patch('/iam/aup/sign'); } } })(); \ No newline at end of file From 0568a1d203e177a1a0a823b81e6c0d595a67c9e3 Mon Sep 17 00:00:00 2001 From: Manoj Garai Date: Tue, 30 Apr 2024 14:32:26 +0100 Subject: [PATCH 11/22] Test user re-sign AUP API --- .../api/aup/AupSignatureIntegrationTests.java | 27 +++++++++++++++++++ 1 file changed, 27 insertions(+) diff --git a/iam-login-service/src/test/java/it/infn/mw/iam/test/api/aup/AupSignatureIntegrationTests.java b/iam-login-service/src/test/java/it/infn/mw/iam/test/api/aup/AupSignatureIntegrationTests.java index d094be313..faaf1b5a2 100644 --- a/iam-login-service/src/test/java/it/infn/mw/iam/test/api/aup/AupSignatureIntegrationTests.java +++ b/iam-login-service/src/test/java/it/infn/mw/iam/test/api/aup/AupSignatureIntegrationTests.java @@ -276,4 +276,31 @@ public void updateAupWithRightScope() throws Exception { .andExpect(jsonPath("$.error", equalTo("Account not found for id: 1234"))); } + + @Test + @WithMockUser(username = "test", roles = {"USER"}) + public void userCanResignAup() throws Exception { + IamAup aup = buildDefaultAup(); + aupRepo.save(aup); + + Date now = new Date(); + mockTimeProvider.setTime(now.getTime()); + mvc.perform(post("/iam/aup/signature").with(user("test").roles("USER"))) + .andExpect(status().isCreated()); + + Date updateTime = new Date(); + mockTimeProvider.setTime(updateTime.getTime()); + mvc.perform(patch("/iam/aup/sign")).andExpect(status().isOk()); + + String sigString = mvc.perform(get("/iam/aup/signature/{accountId}", TEST_USER_UUID)) + .andExpect(status().isOk()) + .andExpect(jsonPath("$.signatureTime").exists()) + .andReturn() + .getResponse() + .getContentAsString(); + + AupSignatureDTO sig = mapper.readValue(sigString, AupSignatureDTO.class); + assertThat(sig.getSignatureTime(), new DateEqualModulo1Second(updateTime)); + } + } From a4c80710980003a1238aec8eea7810f4d84ddb9a Mon Sep 17 00:00:00 2001 From: Manoj Garai Date: Tue, 30 Apr 2024 15:39:29 +0100 Subject: [PATCH 12/22] Test exception in absence of user AUP --- .../api/aup/AupSignatureIntegrationTests.java | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/iam-login-service/src/test/java/it/infn/mw/iam/test/api/aup/AupSignatureIntegrationTests.java b/iam-login-service/src/test/java/it/infn/mw/iam/test/api/aup/AupSignatureIntegrationTests.java index faaf1b5a2..141cfd1be 100644 --- a/iam-login-service/src/test/java/it/infn/mw/iam/test/api/aup/AupSignatureIntegrationTests.java +++ b/iam-login-service/src/test/java/it/infn/mw/iam/test/api/aup/AupSignatureIntegrationTests.java @@ -17,6 +17,8 @@ import static org.hamcrest.MatcherAssert.assertThat; import static org.hamcrest.Matchers.equalTo; +import static org.junit.Assert.assertEquals; +import static org.junit.Assert.assertTrue; import static org.springframework.http.MediaType.APPLICATION_JSON; import static org.springframework.security.test.web.servlet.request.SecurityMockMvcRequestPostProcessors.user; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.delete; @@ -45,6 +47,7 @@ import it.infn.mw.iam.persistence.model.IamAup; import it.infn.mw.iam.persistence.repository.IamAccountRepository; import it.infn.mw.iam.persistence.repository.IamAupRepository; +import it.infn.mw.iam.persistence.repository.IamAupSignatureNotFoundError; import it.infn.mw.iam.test.util.DateEqualModulo1Second; import it.infn.mw.iam.test.util.MockTimeProvider; import it.infn.mw.iam.test.util.WithAnonymousUser; @@ -303,4 +306,17 @@ public void userCanResignAup() throws Exception { assertThat(sig.getSignatureTime(), new DateEqualModulo1Second(updateTime)); } + @Test + @WithMockUser(username = "test", roles = {"USER"}) + public void withoutAUPResignAupThrowsException() throws Exception { + IamAup aup = buildDefaultAup(); + aupRepo.save(aup); + + Date updateTime = new Date(); + mockTimeProvider.setTime(updateTime.getTime()); + mvc.perform(patch("/iam/aup/sign")).andExpect(status().isNotFound()) + .andExpect(result -> assertTrue(result.getResolvedException() instanceof IamAupSignatureNotFoundError)) + .andExpect(result -> assertEquals("AUP signature not found for user 'test'", result.getResolvedException().getMessage())); + } + } From 2f730247ca651e91e349a9677be2797490c3e448 Mon Sep 17 00:00:00 2001 From: Manoj Garai Date: Fri, 10 May 2024 09:44:19 +0100 Subject: [PATCH 13/22] Remove duplicate code using responseFromOptional method --- .../find/DefaultFindAccountService.java | 20 +++++++------------ .../api/account/find/FindAccountService.java | 5 +---- .../it/infn/mw/iam/api/utils/FindUtils.java | 8 ++++++-- 3 files changed, 14 insertions(+), 19 deletions(-) diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/api/account/find/DefaultFindAccountService.java b/iam-login-service/src/main/java/it/infn/mw/iam/api/account/find/DefaultFindAccountService.java index db350a3c5..e2b4fde80 100644 --- a/iam-login-service/src/main/java/it/infn/mw/iam/api/account/find/DefaultFindAccountService.java +++ b/iam-login-service/src/main/java/it/infn/mw/iam/api/account/find/DefaultFindAccountService.java @@ -16,6 +16,7 @@ package it.infn.mw.iam.api.account.find; import static it.infn.mw.iam.api.utils.FindUtils.responseFromPage; +import static it.infn.mw.iam.api.utils.FindUtils.responseFromOptional; import java.util.Optional; import java.util.function.Supplier; @@ -28,7 +29,6 @@ import it.infn.mw.iam.api.scim.converter.UserConverter; import it.infn.mw.iam.api.scim.exception.IllegalArgumentException; import it.infn.mw.iam.api.scim.model.ScimListResponse; -import it.infn.mw.iam.api.scim.model.ScimListResponse.ScimListResponseBuilder; import it.infn.mw.iam.api.scim.model.ScimUser; import it.infn.mw.iam.persistence.model.IamAccount; import it.infn.mw.iam.persistence.model.IamGroup; @@ -64,18 +64,13 @@ public ScimListResponse findAccountByLabel(String labelName, String la @Override public ScimListResponse findAccountByEmail(String emailAddress) { Optional account = repo.findByEmail(emailAddress); - - ScimListResponseBuilder builder = ScimListResponse.builder(); - account.ifPresent(a -> builder.singleResource(converter.dtoFromEntity(a))); - return builder.build(); + return responseFromOptional(account, converter); } @Override public ScimListResponse findAccountByUsername(String username) { Optional account = repo.findByUsername(username); - ScimListResponseBuilder builder = ScimListResponse.builder(); - account.ifPresent(a -> builder.singleResource(converter.dtoFromEntity(a))); - return builder.build(); + return responseFromOptional(account, converter); } @Override @@ -114,9 +109,7 @@ private Supplier groupNotFoundError(String groupNameOr @Override public ScimListResponse findAccountByCertificateSubject(String certSubject) { Optional account = repo.findByCertificateSubject(certSubject); - ScimListResponseBuilder builder = ScimListResponse.builder(); - account.ifPresent(a -> builder.singleResource(converter.dtoFromEntity(a))); - return builder.build(); + return responseFromOptional(account, converter); } @Override @@ -143,7 +136,8 @@ public ScimListResponse findAccountByGroupUuidWithFilter(String groupU return responseFromPage(results, converter, pageable); } - public Optional findAccountByUuid(String uuid) { - return repo.findByUuid(uuid); + public ScimListResponse findAccountByUuid(String uuid) { + Optional account = repo.findByUuid(uuid); + return responseFromOptional(account, converter); } } diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/api/account/find/FindAccountService.java b/iam-login-service/src/main/java/it/infn/mw/iam/api/account/find/FindAccountService.java index aca1e154b..67825b9d6 100644 --- a/iam-login-service/src/main/java/it/infn/mw/iam/api/account/find/FindAccountService.java +++ b/iam-login-service/src/main/java/it/infn/mw/iam/api/account/find/FindAccountService.java @@ -15,13 +15,10 @@ */ package it.infn.mw.iam.api.account.find; -import java.util.Optional; - import org.springframework.data.domain.Pageable; import it.infn.mw.iam.api.scim.model.ScimListResponse; import it.infn.mw.iam.api.scim.model.ScimUser; -import it.infn.mw.iam.persistence.model.IamAccount; public interface FindAccountService { @@ -49,5 +46,5 @@ ScimListResponse findAccountByGroupUuidWithFilter(String groupUuid, St ScimListResponse findAccountNotInGroupWithFilter(String groupUuid, String filter, Pageable pageable); - Optional findAccountByUuid(String uuid); + ScimListResponse findAccountByUuid(String uuid); } diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/api/utils/FindUtils.java b/iam-login-service/src/main/java/it/infn/mw/iam/api/utils/FindUtils.java index 5c9b78efa..e45cdd3c6 100644 --- a/iam-login-service/src/main/java/it/infn/mw/iam/api/utils/FindUtils.java +++ b/iam-login-service/src/main/java/it/infn/mw/iam/api/utils/FindUtils.java @@ -17,6 +17,7 @@ import java.util.ArrayList; import java.util.List; +import java.util.Optional; import org.springframework.data.domain.Page; import org.springframework.data.domain.Pageable; @@ -43,6 +44,9 @@ public static ScimListResponse responseFromPage(Page results, return builder.build(); } - - + public static ScimListResponse responseFromOptional(Optional account, Converter converter) { + ScimListResponseBuilder builder = ScimListResponse.builder(); + account.ifPresent(a -> builder.singleResource(converter.dtoFromEntity(a))); + return builder.build(); + } } From 66ce06bd2d3d1621fe257365cdfb36117809631d Mon Sep 17 00:00:00 2001 From: Manoj Garai Date: Fri, 10 May 2024 09:47:22 +0100 Subject: [PATCH 14/22] Return ScimUser to match interface design --- .../account/find/FindAccountController.java | 28 ++----------------- .../clients/client/client.component.js | 2 +- .../clientslist/clientslist.component.js | 2 +- .../user/myclients/myclients.component.js | 2 +- .../dashboard-app/services/find.service.js | 2 +- 5 files changed, 7 insertions(+), 29 deletions(-) diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/api/account/find/FindAccountController.java b/iam-login-service/src/main/java/it/infn/mw/iam/api/account/find/FindAccountController.java index 0e27d2b1e..1df4b6af5 100644 --- a/iam-login-service/src/main/java/it/infn/mw/iam/api/account/find/FindAccountController.java +++ b/iam-login-service/src/main/java/it/infn/mw/iam/api/account/find/FindAccountController.java @@ -129,31 +129,9 @@ public ListResponseDTO findNotInGroup(@PathVariable String groupUuid, } } - @GetMapping(FIND_BY_UUID_RESOURCE) + @GetMapping(value = FIND_BY_UUID_RESOURCE, produces = ScimConstants.SCIM_CONTENT_TYPE) @PreAuthorize("#iam.hasScope('iam:admin.read') or #iam.hasDashboardRole('ROLE_ADMIN') or hasRole('USER')") - public JSONObject findByUuid(@PathVariable String accountUuid) { - Optional iamAccount = service.findAccountByUuid(accountUuid); - if(iamAccount.isPresent()){ - return getIamAccountJson(iamAccount.get()); - } else{ - throw NoSuchAccountError.forUuid(accountUuid); - } - } - - @ResponseStatus(value = HttpStatus.NOT_FOUND) - @ExceptionHandler(NoSuchAccountError.class) - @ResponseBody - @PreAuthorize("#iam.hasScope('iam:admin.read') or #iam.hasDashboardRole('ROLE_ADMIN') or hasRole('USER')") - public ErrorDTO accountNotFoundError(HttpServletRequest req, Exception ex) { - return ErrorDTO.fromString(ex.getMessage()); - } - - private JSONObject getIamAccountJson(IamAccount iamAccount) { - JSONObject iamAccountJson = new JSONObject(); - iamAccountJson.put("id", iamAccount.getId()); - iamAccountJson.put("accountUuid", iamAccount.getUuid()); - iamAccountJson.put("username", iamAccount.getUsername()); - iamAccountJson.put("active", iamAccount.isActive()); - return iamAccountJson; + public ListResponseDTO findByUuid(@PathVariable String accountUuid) { + return service.findAccountByUuid(accountUuid); } } diff --git a/iam-login-service/src/main/webapp/resources/iam/apps/dashboard-app/components/clients/client/client.component.js b/iam-login-service/src/main/webapp/resources/iam/apps/dashboard-app/components/clients/client/client.component.js index 2084a890c..a3f4002bd 100644 --- a/iam-login-service/src/main/webapp/resources/iam/apps/dashboard-app/components/clients/client/client.component.js +++ b/iam-login-service/src/main/webapp/resources/iam/apps/dashboard-app/components/clients/client/client.component.js @@ -135,7 +135,7 @@ function getClientStatusMessage(){ FindService.findAccountByUuid(self.clientVal.status_changed_by).then(function(res){ - self.clientStatusMessage = "Suspended by " + res.username + " on " + getFormatedDate(self.clientVal.status_changed_on); + self.clientStatusMessage = "Suspended by " + res.userName + " on " + getFormatedDate(self.clientVal.status_changed_on); }).catch(function (res) { console.debug("Error retrieving user account!", res); }); diff --git a/iam-login-service/src/main/webapp/resources/iam/apps/dashboard-app/components/clients/clientslist/clientslist.component.js b/iam-login-service/src/main/webapp/resources/iam/apps/dashboard-app/components/clients/clientslist/clientslist.component.js index 50ad86daf..a22ceb89c 100644 --- a/iam-login-service/src/main/webapp/resources/iam/apps/dashboard-app/components/clients/clientslist/clientslist.component.js +++ b/iam-login-service/src/main/webapp/resources/iam/apps/dashboard-app/components/clients/clientslist/clientslist.component.js @@ -122,7 +122,7 @@ function getClientStatusMessage(client){ FindService.findAccountByUuid(client.status_changed_by).then(function(res){ - self.clientStatusMessage = "Suspended by " + res.username + " on " + getFormatedDate(client.status_changed_on); + self.clientStatusMessage = "Suspended by " + res.userName + " on " + getFormatedDate(client.status_changed_on); }).catch(function (res) { console.debug("Error retrieving user account!", res); }); diff --git a/iam-login-service/src/main/webapp/resources/iam/apps/dashboard-app/components/user/myclients/myclients.component.js b/iam-login-service/src/main/webapp/resources/iam/apps/dashboard-app/components/user/myclients/myclients.component.js index 0e4fbaf35..917708f2a 100644 --- a/iam-login-service/src/main/webapp/resources/iam/apps/dashboard-app/components/user/myclients/myclients.component.js +++ b/iam-login-service/src/main/webapp/resources/iam/apps/dashboard-app/components/user/myclients/myclients.component.js @@ -122,7 +122,7 @@ function getClientStatusMessage(client){ FindService.findAccountByUuid(client.status_changed_by).then(function(res){ - self.clientStatusMessage = "Suspended by " + res.username + " on " + getFormatedDate(client.status_changed_on); + self.clientStatusMessage = "Suspended by " + res.userName + " on " + getFormatedDate(client.status_changed_on); }).catch(function (res) { console.debug("Error retrieving user account!", res); }); diff --git a/iam-login-service/src/main/webapp/resources/iam/apps/dashboard-app/services/find.service.js b/iam-login-service/src/main/webapp/resources/iam/apps/dashboard-app/services/find.service.js index df4467610..017095c8b 100644 --- a/iam-login-service/src/main/webapp/resources/iam/apps/dashboard-app/services/find.service.js +++ b/iam-login-service/src/main/webapp/resources/iam/apps/dashboard-app/services/find.service.js @@ -51,7 +51,7 @@ function findAccountByUuid(accountUuid) { var url = "/iam/account/find/byuuid/" + accountUuid; return $http.get(url).then(function (result) { - return result.data; + return result.data.Resources[0]; }).catch(function (error) { console.error("Error loading account details: ", error); return $q.reject(error); From 56be86c65a7b4a9c511d3c74cfc1d71c380c3a92 Mon Sep 17 00:00:00 2001 From: Manoj Garai Date: Fri, 10 May 2024 09:48:31 +0100 Subject: [PATCH 15/22] Use different endpoint to enable and disable client --- .../ClientManagementAPIController.java | 26 ++++---- .../DefaultClientRegistrationService.java | 60 +++++++++---------- .../client/status/client.status.component.js | 7 +-- .../dashboard-app/services/clients.service.js | 15 ++++- 4 files changed, 60 insertions(+), 48 deletions(-) diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/api/client/management/ClientManagementAPIController.java b/iam-login-service/src/main/java/it/infn/mw/iam/api/client/management/ClientManagementAPIController.java index 2c377ef43..c6309bef7 100644 --- a/iam-login-service/src/main/java/it/infn/mw/iam/api/client/management/ClientManagementAPIController.java +++ b/iam-login-service/src/main/java/it/infn/mw/iam/api/client/management/ClientManagementAPIController.java @@ -44,9 +44,8 @@ import org.springframework.web.bind.annotation.RestController; import com.fasterxml.jackson.annotation.JsonView; -import com.google.gson.JsonObject; -import com.google.gson.JsonParser; +import it.infn.mw.iam.api.account.AccountUtils; import it.infn.mw.iam.api.client.error.InvalidPaginationRequest; import it.infn.mw.iam.api.client.error.NoSuchClient; import it.infn.mw.iam.api.client.management.service.ClientManagementService; @@ -56,6 +55,7 @@ import it.infn.mw.iam.api.common.PagingUtils; import it.infn.mw.iam.api.common.client.RegisteredClientDTO; import it.infn.mw.iam.api.scim.model.ScimUser; +import it.infn.mw.iam.persistence.model.IamAccount; @RestController @RequestMapping(ClientManagementAPIController.ENDPOINT) @@ -64,9 +64,11 @@ public class ClientManagementAPIController { public static final String ENDPOINT = "/iam/api/clients"; private final ClientManagementService managementService; + private final AccountUtils accountUtils; - public ClientManagementAPIController(ClientManagementService managementService) { + public ClientManagementAPIController(ClientManagementService managementService, AccountUtils accountUtils) { this.managementService = managementService; + this.accountUtils = accountUtils; } @PostMapping @@ -143,14 +145,18 @@ public RegisteredClientDTO updateClient(@PathVariable String clientId, return managementService.updateClient(clientId, client); } - @PatchMapping("/{clientId}/status") + @PatchMapping("/{clientId}/enable") @PreAuthorize("#iam.hasScope('iam:admin.write') or #iam.hasDashboardRole('ROLE_ADMIN')") - public void updateClientStatus(@PathVariable String clientId, - @RequestBody String body) { - JsonObject jsonObject = JsonParser.parseString(body).getAsJsonObject(); - boolean status = jsonObject.get("status").getAsBoolean(); - String userId = jsonObject.get("userId").getAsString(); - managementService.updateClientStatus(clientId, status, userId); + public void enableClient(@PathVariable String clientId) { + Optional accpunt = accountUtils.getAuthenticatedUserAccount(); + accpunt.ifPresent(a -> managementService.updateClientStatus(clientId, true, a.getUuid())); + } + + @PatchMapping("/{clientId}/disable") + @PreAuthorize("#iam.hasScope('iam:admin.write') or #iam.hasDashboardRole('ROLE_ADMIN')") + public void disableClient(@PathVariable String clientId) { + Optional accpunt = accountUtils.getAuthenticatedUserAccount(); + accpunt.ifPresent(a -> managementService.updateClientStatus(clientId, false, a.getUuid())); } @PostMapping("/{clientId}/secret") diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/api/client/registration/service/DefaultClientRegistrationService.java b/iam-login-service/src/main/java/it/infn/mw/iam/api/client/registration/service/DefaultClientRegistrationService.java index a79ab9af2..cb47ce787 100644 --- a/iam-login-service/src/main/java/it/infn/mw/iam/api/client/registration/service/DefaultClientRegistrationService.java +++ b/iam-login-service/src/main/java/it/infn/mw/iam/api/client/registration/service/DefaultClientRegistrationService.java @@ -396,39 +396,37 @@ public RegisteredClientDTO updateClient(String clientId, RegisteredClientDTO req ClientDetailsEntity oldClient = lookupClient(clientId, authentication).orElseThrow(clientNotFound(clientId)); - + if(!oldClient.isActive()){ + throw new ClientSuspended("Client " + clientId + " is suspended!"); + } checkAllowedGrantTypesOnUpdate(request, authentication, oldClient); cleanupRequestedScopesOnUpdate(request, authentication, oldClient); - if(oldClient.isActive()){ - ClientDetailsEntity newClient = converter.entityFromRegistrationRequest(request); - newClient.setId(oldClient.getId()); - newClient.setClientSecret(oldClient.getClientSecret()); - newClient.setAccessTokenValiditySeconds(oldClient.getAccessTokenValiditySeconds()); - newClient.setIdTokenValiditySeconds(oldClient.getIdTokenValiditySeconds()); - newClient.setRefreshTokenValiditySeconds(oldClient.getRefreshTokenValiditySeconds()); - newClient.setDeviceCodeValiditySeconds(oldClient.getDeviceCodeValiditySeconds()); - newClient.setDynamicallyRegistered(true); - newClient.setAllowIntrospection(oldClient.isAllowIntrospection()); - newClient.setAuthorities(oldClient.getAuthorities()); - newClient.setCreatedAt(oldClient.getCreatedAt()); - newClient.setReuseRefreshToken(oldClient.isReuseRefreshToken()); - newClient.setActive(oldClient.isActive()); - - ClientDetailsEntity savedClient = clientService.updateClient(newClient); - - eventPublisher.publishEvent(new ClientUpdatedEvent(this, savedClient)); - - RegisteredClientDTO response = converter.registrationResponseFromClient(savedClient); - - maybeUpdateRegistrationAccessToken(savedClient, authentication).ifPresent(t -> { - eventPublisher.publishEvent(new ClientRegistrationAccessTokenRotatedEvent(this, savedClient)); - response.setRegistrationAccessToken(t); - }); - return response; - } else { - throw new ClientSuspended("Client " + clientId + " is suspended!"); - } - + + ClientDetailsEntity newClient = converter.entityFromRegistrationRequest(request); + newClient.setId(oldClient.getId()); + newClient.setClientSecret(oldClient.getClientSecret()); + newClient.setAccessTokenValiditySeconds(oldClient.getAccessTokenValiditySeconds()); + newClient.setIdTokenValiditySeconds(oldClient.getIdTokenValiditySeconds()); + newClient.setRefreshTokenValiditySeconds(oldClient.getRefreshTokenValiditySeconds()); + newClient.setDeviceCodeValiditySeconds(oldClient.getDeviceCodeValiditySeconds()); + newClient.setDynamicallyRegistered(true); + newClient.setAllowIntrospection(oldClient.isAllowIntrospection()); + newClient.setAuthorities(oldClient.getAuthorities()); + newClient.setCreatedAt(oldClient.getCreatedAt()); + newClient.setReuseRefreshToken(oldClient.isReuseRefreshToken()); + newClient.setActive(oldClient.isActive()); + + ClientDetailsEntity savedClient = clientService.updateClient(newClient); + + eventPublisher.publishEvent(new ClientUpdatedEvent(this, savedClient)); + + RegisteredClientDTO response = converter.registrationResponseFromClient(savedClient); + + maybeUpdateRegistrationAccessToken(savedClient, authentication).ifPresent(t -> { + eventPublisher.publishEvent(new ClientRegistrationAccessTokenRotatedEvent(this, savedClient)); + response.setRegistrationAccessToken(t); + }); + return response; } @Override diff --git a/iam-login-service/src/main/webapp/resources/iam/apps/dashboard-app/components/clients/client/status/client.status.component.js b/iam-login-service/src/main/webapp/resources/iam/apps/dashboard-app/components/clients/client/status/client.status.component.js index 6cb82457f..2c8512814 100644 --- a/iam-login-service/src/main/webapp/resources/iam/apps/dashboard-app/components/clients/client/status/client.status.component.js +++ b/iam-login-service/src/main/webapp/resources/iam/apps/dashboard-app/components/clients/client/status/client.status.component.js @@ -56,23 +56,22 @@ }; self.enableClient = function() { - return ClientsService.setClientActiveStatus(self.client.client_id, true, self.loggedUserId) + return ClientsService.enableClient(self.client.client_id) .then(self.handleSuccess) .catch(self.handleError); }; self.disableClient = function() { - return ClientsService.setClientActiveStatus(self.client.client_id, false, self.loggedUserId) + return ClientsService.disableClient(self.client.client_id) .then(self.handleSuccess) .catch(self.handleError); }; - self.openDialog = function(loggedUserId) { + self.openDialog = function() { var modalOptions = null; var updateStatusFunc = null; - self.loggedUserId = loggedUserId; if (self.client.active) { modalOptions = { diff --git a/iam-login-service/src/main/webapp/resources/iam/apps/dashboard-app/services/clients.service.js b/iam-login-service/src/main/webapp/resources/iam/apps/dashboard-app/services/clients.service.js index 4c02e5e8e..f9eec42d3 100644 --- a/iam-login-service/src/main/webapp/resources/iam/apps/dashboard-app/services/clients.service.js +++ b/iam-login-service/src/main/webapp/resources/iam/apps/dashboard-app/services/clients.service.js @@ -45,7 +45,8 @@ removeClientOwner: removeClientOwner, newClient: newClient, getClientList: getClientList, - setClientActiveStatus: setClientActiveStatus + enableClient: enableClient, + disableClient: disableClient }; return service; @@ -177,8 +178,16 @@ return newClient; } - function setClientActiveStatus(clientId, isActive, userId){ - return $http.patch(endpoint(clientId) + "/status", "{\"status\": "+ isActive + ",\"userId\":" + userId + "}", defaultRequestConfig).then(function (res) { + function enableClient(clientId){ + return $http.patch(endpoint(clientId) + "/enable").then(function (res) { + return res.data; + }).catch(function (res) { + return $q.reject(res); + }); + } + + function disableClient(clientId){ + return $http.patch(endpoint(clientId) + "/disable").then(function (res) { return res.data; }).catch(function (res) { return $q.reject(res); From 21779fc86f547319c54c3ff30a76ca95007dffb2 Mon Sep 17 00:00:00 2001 From: Manoj Garai Date: Fri, 10 May 2024 16:32:46 +0100 Subject: [PATCH 16/22] Fix test cases --- .../find/FindAccountIntegrationTests.java | 17 +++++++-------- .../ClientManagementAPIIntegrationTests.java | 21 ++++++++++++++++--- 2 files changed, 25 insertions(+), 13 deletions(-) diff --git a/iam-login-service/src/test/java/it/infn/mw/iam/test/api/account/find/FindAccountIntegrationTests.java b/iam-login-service/src/test/java/it/infn/mw/iam/test/api/account/find/FindAccountIntegrationTests.java index c9b5fa582..0b35d0e31 100644 --- a/iam-login-service/src/test/java/it/infn/mw/iam/test/api/account/find/FindAccountIntegrationTests.java +++ b/iam-login-service/src/test/java/it/infn/mw/iam/test/api/account/find/FindAccountIntegrationTests.java @@ -43,7 +43,6 @@ import org.springframework.test.context.junit4.SpringRunner; import org.springframework.test.web.servlet.MockMvc; -import it.infn.mw.iam.api.common.error.NoSuchAccountError; import it.infn.mw.iam.core.group.IamGroupService; import it.infn.mw.iam.core.user.IamAccountService; import it.infn.mw.iam.persistence.model.IamAccount; @@ -305,19 +304,17 @@ public void findByUUIDWorks() throws Exception { mvc.perform(get(FIND_BY_UUID_RESOURCE, testAccount.getUuid())) .andExpect(OK) - .andExpect(jsonPath("$.id", is(testAccount.getId()), Long.class)) - .andExpect(jsonPath("$.accountUuid", is(testAccount.getUuid()))) - .andExpect(jsonPath("$.username", is(testAccount.getUsername()))) - .andExpect(jsonPath("$.active", is(testAccount.isActive()))); + .andExpect(jsonPath("$.Resources[0].id", is(testAccount.getUuid()))) + .andExpect(jsonPath("$.Resources[0].userName", is(testAccount.getUsername()))) + .andExpect(jsonPath("$.Resources[0].active", is(testAccount.isActive()))); } @Test @WithMockUser(username = "test", roles = "USER") - public void findByUUIDThorowsException() throws Exception { + public void totalResultDoesNotExistForUnknownUUID() throws Exception { mvc.perform(get(FIND_BY_UUID_RESOURCE, "unknown_uuid")) - .andExpect(status().isNotFound()) - .andExpect(result -> assertTrue(result.getResolvedException() instanceof NoSuchAccountError)) - .andExpect(result -> assertEquals("Account not found for id 'unknown_uuid'", result.getResolvedException().getMessage())); + .andExpect(OK) + .andExpect(jsonPath("$.totalResults").doesNotExist()) + .andExpect(jsonPath("$.Resources", emptyIterable())); } - } diff --git a/iam-login-service/src/test/java/it/infn/mw/iam/test/api/client/ClientManagementAPIIntegrationTests.java b/iam-login-service/src/test/java/it/infn/mw/iam/test/api/client/ClientManagementAPIIntegrationTests.java index 757f778f9..2b40da102 100644 --- a/iam-login-service/src/test/java/it/infn/mw/iam/test/api/client/ClientManagementAPIIntegrationTests.java +++ b/iam-login-service/src/test/java/it/infn/mw/iam/test/api/client/ClientManagementAPIIntegrationTests.java @@ -296,14 +296,29 @@ public void negativeRefreshTokenLifetimesSetToInfinite() throws Exception { @Test @WithMockUser(username = "admin", roles = {"ADMIN", "USER"}) - public void clientStatusUpdateWorks() throws Exception { + public void setClientEnableWorks() throws Exception { mvc.perform(get(ClientManagementAPIController.ENDPOINT + "/client")) .andExpect(OK) .andExpect(jsonPath("$.active").value(true)); - mvc.perform(patch(ClientManagementAPIController.ENDPOINT + "/{clientId}/status", "client") - .content("{\"status\": false, \"userId\": userId}") + mvc.perform(patch(ClientManagementAPIController.ENDPOINT + "/{clientId}/enable", "client") + ).andExpect(OK); + + mvc.perform(get(ClientManagementAPIController.ENDPOINT + "/client")) + .andExpect(OK) + .andExpect(jsonPath("$.active").value(true)); + } + + @Test + @WithMockUser(username = "admin", roles = {"ADMIN", "USER"}) + public void setClientDisableWorks() throws Exception { + + mvc.perform(get(ClientManagementAPIController.ENDPOINT + "/client")) + .andExpect(OK) + .andExpect(jsonPath("$.active").value(true)); + + mvc.perform(patch(ClientManagementAPIController.ENDPOINT + "/{clientId}/disable", "client") ).andExpect(OK); mvc.perform(get(ClientManagementAPIController.ENDPOINT + "/client")) From 912a9630b9a8df4a5a3ef3756de2136d2feaeb25 Mon Sep 17 00:00:00 2001 From: Manoj Garai Date: Wed, 15 May 2024 15:10:07 +0100 Subject: [PATCH 17/22] Fix typo --- .../client/management/ClientManagementAPIController.java | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/api/client/management/ClientManagementAPIController.java b/iam-login-service/src/main/java/it/infn/mw/iam/api/client/management/ClientManagementAPIController.java index c6309bef7..406891a30 100644 --- a/iam-login-service/src/main/java/it/infn/mw/iam/api/client/management/ClientManagementAPIController.java +++ b/iam-login-service/src/main/java/it/infn/mw/iam/api/client/management/ClientManagementAPIController.java @@ -148,15 +148,15 @@ public RegisteredClientDTO updateClient(@PathVariable String clientId, @PatchMapping("/{clientId}/enable") @PreAuthorize("#iam.hasScope('iam:admin.write') or #iam.hasDashboardRole('ROLE_ADMIN')") public void enableClient(@PathVariable String clientId) { - Optional accpunt = accountUtils.getAuthenticatedUserAccount(); - accpunt.ifPresent(a -> managementService.updateClientStatus(clientId, true, a.getUuid())); + Optional account = accountUtils.getAuthenticatedUserAccount(); + account.ifPresent(a -> managementService.updateClientStatus(clientId, true, a.getUuid())); } @PatchMapping("/{clientId}/disable") @PreAuthorize("#iam.hasScope('iam:admin.write') or #iam.hasDashboardRole('ROLE_ADMIN')") public void disableClient(@PathVariable String clientId) { - Optional accpunt = accountUtils.getAuthenticatedUserAccount(); - accpunt.ifPresent(a -> managementService.updateClientStatus(clientId, false, a.getUuid())); + Optional account = accountUtils.getAuthenticatedUserAccount(); + account.ifPresent(a -> managementService.updateClientStatus(clientId, false, a.getUuid())); } @PostMapping("/{clientId}/secret") From 26b53a9a2a83329f4cf7ebd3d5d6f5ec28bbc11f Mon Sep 17 00:00:00 2001 From: Manoj Garai Date: Wed, 15 May 2024 15:13:34 +0100 Subject: [PATCH 18/22] Check user updating suspended client --- .../ClientRegistrationApiController.java | 2 +- .../service/DefaultClientRegistrationService.java | 14 +++++++++++--- 2 files changed, 12 insertions(+), 4 deletions(-) diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/api/client/registration/ClientRegistrationApiController.java b/iam-login-service/src/main/java/it/infn/mw/iam/api/client/registration/ClientRegistrationApiController.java index 5f1b1e210..5b8ef1cd6 100644 --- a/iam-login-service/src/main/java/it/infn/mw/iam/api/client/registration/ClientRegistrationApiController.java +++ b/iam-login-service/src/main/java/it/infn/mw/iam/api/client/registration/ClientRegistrationApiController.java @@ -120,7 +120,7 @@ public ErrorDTO noSuchClient(HttpServletRequest req, Exception ex) { return ErrorDTO.fromString(ex.getMessage()); } - @ResponseStatus(value = HttpStatus.BAD_REQUEST) + @ResponseStatus(value = HttpStatus.FORBIDDEN) @ExceptionHandler(ClientSuspended.class) public ErrorDTO clientSuspended(HttpServletRequest req, Exception ex) { return ErrorDTO.fromString(ex.getMessage()); diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/api/client/registration/service/DefaultClientRegistrationService.java b/iam-login-service/src/main/java/it/infn/mw/iam/api/client/registration/service/DefaultClientRegistrationService.java index cb47ce787..e9aa131fb 100644 --- a/iam-login-service/src/main/java/it/infn/mw/iam/api/client/registration/service/DefaultClientRegistrationService.java +++ b/iam-login-service/src/main/java/it/infn/mw/iam/api/client/registration/service/DefaultClientRegistrationService.java @@ -321,6 +321,15 @@ && registrationAccessTokenAuthenticationValidForClientId(client.getClientId(), a return Optional.empty(); } + private void checkUserUpdatingSuspendedClient(Authentication authentication, ClientDetailsEntity oldClient) { + if (accountUtils.isAdmin(authentication)) { + return; + } + if(!oldClient.isActive()){ + throw new ClientSuspended("Client " + oldClient.getClientId() + " is suspended!"); + } + } + @Validated(OnDynamicClientRegistration.class) @Override public RegisteredClientDTO registerClient(RegisteredClientDTO request, @@ -396,9 +405,8 @@ public RegisteredClientDTO updateClient(String clientId, RegisteredClientDTO req ClientDetailsEntity oldClient = lookupClient(clientId, authentication).orElseThrow(clientNotFound(clientId)); - if(!oldClient.isActive()){ - throw new ClientSuspended("Client " + clientId + " is suspended!"); - } + + checkUserUpdatingSuspendedClient(authentication, oldClient); checkAllowedGrantTypesOnUpdate(request, authentication, oldClient); cleanupRequestedScopesOnUpdate(request, authentication, oldClient); From 8c980d8d7bf2fa5731908c06413e9992c2bf2177 Mon Sep 17 00:00:00 2001 From: Manoj Garai Date: Wed, 15 May 2024 15:30:06 +0100 Subject: [PATCH 19/22] Remove unused imports --- .../mw/iam/api/account/find/FindAccountController.java | 10 ---------- .../api/account/find/FindAccountIntegrationTests.java | 3 --- 2 files changed, 13 deletions(-) diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/api/account/find/FindAccountController.java b/iam-login-service/src/main/java/it/infn/mw/iam/api/account/find/FindAccountController.java index 1df4b6af5..2c540b8c8 100644 --- a/iam-login-service/src/main/java/it/infn/mw/iam/api/account/find/FindAccountController.java +++ b/iam-login-service/src/main/java/it/infn/mw/iam/api/account/find/FindAccountController.java @@ -20,27 +20,17 @@ import static java.util.Objects.isNull; import static org.springframework.web.bind.annotation.RequestMethod.GET; -import java.util.Optional; - -import javax.servlet.http.HttpServletRequest; - import org.springframework.beans.factory.annotation.Autowired; import org.springframework.data.domain.Pageable; -import org.springframework.http.HttpStatus; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.validation.BindingResult; import org.springframework.validation.annotation.Validated; import org.springframework.web.bind.annotation.*; -import com.nimbusds.jose.shaded.json.JSONObject; - -import it.infn.mw.iam.api.common.ErrorDTO; import it.infn.mw.iam.api.common.ListResponseDTO; -import it.infn.mw.iam.api.common.error.NoSuchAccountError; import it.infn.mw.iam.api.common.form.PaginatedRequestWithFilterForm; import it.infn.mw.iam.api.scim.model.ScimConstants; import it.infn.mw.iam.api.scim.model.ScimUser; -import it.infn.mw.iam.persistence.model.IamAccount; @RestController @PreAuthorize("#iam.hasScope('iam:admin.read') or #iam.hasDashboardRole('ROLE_ADMIN')") diff --git a/iam-login-service/src/test/java/it/infn/mw/iam/test/api/account/find/FindAccountIntegrationTests.java b/iam-login-service/src/test/java/it/infn/mw/iam/test/api/account/find/FindAccountIntegrationTests.java index 0b35d0e31..1fd5c30fa 100644 --- a/iam-login-service/src/test/java/it/infn/mw/iam/test/api/account/find/FindAccountIntegrationTests.java +++ b/iam-login-service/src/test/java/it/infn/mw/iam/test/api/account/find/FindAccountIntegrationTests.java @@ -26,11 +26,8 @@ import static org.hamcrest.CoreMatchers.is; import static org.hamcrest.CoreMatchers.not; import static org.hamcrest.Matchers.emptyIterable; -import static org.junit.jupiter.api.Assertions.assertEquals; -import static org.junit.jupiter.api.Assertions.assertTrue; import static org.springframework.test.web.servlet.request.MockMvcRequestBuilders.get; import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.jsonPath; -import static org.springframework.test.web.servlet.result.MockMvcResultMatchers.status; import java.util.function.Supplier; From 20a283d4b2694940c951eaa5cf2cbab89f6b360e Mon Sep 17 00:00:00 2001 From: Manoj Garai Date: Tue, 21 May 2024 12:04:24 +0100 Subject: [PATCH 20/22] Create sub-component for re-sign AUP --- .../api/aup/AupSignaturePageController.java | 35 --------- .../audit/events/aup/AupResignedEvent.java | 47 ----------- .../webapp/WEB-INF/views/iam/dashboard.jsp | 1 + .../components/aup/aup.resign.component.html | 25 ++++++ .../components/aup/aup.resign.component.js | 78 +++++++++++++++++++ .../components/user/user.component.html | 9 +-- .../components/user/user.component.js | 39 ---------- .../dashboard-app/services/aup.service.js | 8 +- .../templates/home/resignAup.html | 12 +-- .../api/aup/AupSignatureIntegrationTests.java | 42 ---------- 10 files changed, 118 insertions(+), 178 deletions(-) delete mode 100644 iam-login-service/src/main/java/it/infn/mw/iam/audit/events/aup/AupResignedEvent.java create mode 100644 iam-login-service/src/main/webapp/resources/iam/apps/dashboard-app/components/aup/aup.resign.component.html create mode 100644 iam-login-service/src/main/webapp/resources/iam/apps/dashboard-app/components/aup/aup.resign.component.js diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/api/aup/AupSignaturePageController.java b/iam-login-service/src/main/java/it/infn/mw/iam/api/aup/AupSignaturePageController.java index 900d5243e..c3c93079a 100644 --- a/iam-login-service/src/main/java/it/infn/mw/iam/api/aup/AupSignaturePageController.java +++ b/iam-login-service/src/main/java/it/infn/mw/iam/api/aup/AupSignaturePageController.java @@ -29,27 +29,20 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.context.ApplicationEventPublisher; -import org.springframework.http.HttpStatus; import org.springframework.security.access.prepost.PreAuthorize; import org.springframework.security.web.savedrequest.SavedRequest; import org.springframework.stereotype.Controller; -import org.springframework.web.bind.annotation.ExceptionHandler; import org.springframework.web.bind.annotation.RequestMapping; import org.springframework.web.bind.annotation.RequestMethod; -import org.springframework.web.bind.annotation.ResponseBody; -import org.springframework.web.bind.annotation.ResponseStatus; import org.springframework.web.servlet.ModelAndView; import it.infn.mw.iam.api.account.AccountUtils; -import it.infn.mw.iam.api.common.ErrorDTO; -import it.infn.mw.iam.audit.events.aup.AupResignedEvent; import it.infn.mw.iam.audit.events.aup.AupSignedEvent; import it.infn.mw.iam.core.time.TimeProvider; import it.infn.mw.iam.persistence.model.IamAccount; import it.infn.mw.iam.persistence.model.IamAup; import it.infn.mw.iam.persistence.model.IamAupSignature; import it.infn.mw.iam.persistence.repository.IamAupRepository; -import it.infn.mw.iam.persistence.repository.IamAupSignatureNotFoundError; import it.infn.mw.iam.persistence.repository.IamAupSignatureRepository; @Controller @@ -135,34 +128,6 @@ public ModelAndView signAup(HttpServletRequest request, HttpServletResponse resp return new ModelAndView("redirect:/dashboard"); } - - @PreAuthorize("hasRole('USER')") - @RequestMapping(method = RequestMethod.PATCH, value = "/iam/aup/sign") - public void resignAup(HttpServletRequest request, HttpServletResponse response) { - - Optional aup = repo.findDefaultAup(); - IamAccount account = null; - - if (aup.isPresent()) { - Date now = new Date(timeProvider.currentTimeMillis()); - account = accountUtils.getAuthenticatedUserAccount().orElseThrow( - () -> new IllegalStateException("No iam account found for authenticated user")); - - IamAupSignature signature = signatureRepo.updateSignatureForAccount(account, now); - - publisher.publishEvent(new AupResignedEvent(this, signature)); - } else { - throw new IamAupSignatureNotFoundError(account); - } - } - - @ResponseStatus(value = HttpStatus.NOT_FOUND) - @ExceptionHandler(IamAupSignatureNotFoundError.class) - @ResponseBody - @PreAuthorize("hasRole('USER')") - public ErrorDTO accountNotFoundError(HttpServletRequest req, Exception ex) { - return ErrorDTO.fromString(ex.getMessage()); - } } diff --git a/iam-login-service/src/main/java/it/infn/mw/iam/audit/events/aup/AupResignedEvent.java b/iam-login-service/src/main/java/it/infn/mw/iam/audit/events/aup/AupResignedEvent.java deleted file mode 100644 index bc1049726..000000000 --- a/iam-login-service/src/main/java/it/infn/mw/iam/audit/events/aup/AupResignedEvent.java +++ /dev/null @@ -1,47 +0,0 @@ -/** - * Copyright (c) Istituto Nazionale di Fisica Nucleare (INFN). 2016-2021 - * - * 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. - */ -package it.infn.mw.iam.audit.events.aup; - -import static java.lang.String.format; - -import com.fasterxml.jackson.databind.annotation.JsonSerialize; - -import it.infn.mw.iam.audit.events.IamAuditApplicationEvent; -import it.infn.mw.iam.audit.utils.IamAupSignatureSerializer; -import it.infn.mw.iam.persistence.model.IamAupSignature; - -public class AupResignedEvent extends IamAuditApplicationEvent { - - /** - * - */ - private static final long serialVersionUID = 1L; - - @JsonSerialize(using=IamAupSignatureSerializer.class) - final IamAupSignature signature; - - public AupResignedEvent(Object source, IamAupSignature signature) { - super(IamEventCategory.AUP, source, format("User %s re-signed the AUP", - signature.getAccount().getUsername())); - this.signature = signature; - - } - - public IamAupSignature getSignature() { - return signature; - } - -} diff --git a/iam-login-service/src/main/webapp/WEB-INF/views/iam/dashboard.jsp b/iam-login-service/src/main/webapp/WEB-INF/views/iam/dashboard.jsp index 1ef1459d3..ea18ecbd7 100644 --- a/iam-login-service/src/main/webapp/WEB-INF/views/iam/dashboard.jsp +++ b/iam-login-service/src/main/webapp/WEB-INF/views/iam/dashboard.jsp @@ -169,6 +169,7 @@ + diff --git a/iam-login-service/src/main/webapp/resources/iam/apps/dashboard-app/components/aup/aup.resign.component.html b/iam-login-service/src/main/webapp/resources/iam/apps/dashboard-app/components/aup/aup.resign.component.html new file mode 100644 index 000000000..cacfc1e2e --- /dev/null +++ b/iam-login-service/src/main/webapp/resources/iam/apps/dashboard-app/components/aup/aup.resign.component.html @@ -0,0 +1,25 @@ + + \ No newline at end of file diff --git a/iam-login-service/src/main/webapp/resources/iam/apps/dashboard-app/components/aup/aup.resign.component.js b/iam-login-service/src/main/webapp/resources/iam/apps/dashboard-app/components/aup/aup.resign.component.js new file mode 100644 index 000000000..e17220e0f --- /dev/null +++ b/iam-login-service/src/main/webapp/resources/iam/apps/dashboard-app/components/aup/aup.resign.component.js @@ -0,0 +1,78 @@ +/* + * Copyright (c) Istituto Nazionale di Fisica Nucleare (INFN). 2016-2021 + * + * 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. + */ + +(function () { + 'use strict'; + + function ResignModalController($scope, $uibModalInstance, toaster, AupService, user) { + var self = this; + self.enabled = true; + self.user = user; + + self.cancel = function() { + $uibModalInstance.close('Cancelled'); + }; + + self.submit = function() { + self.error = undefined; + self.enabled = false; + AupService.resignAup() + .then(function(res) { + $uibModalInstance.close('AUP signature re-signed succesfully'); + self.enabled = true; + }, function(res) { + self.error = res.data.error; + self.enabled = true; + toaster.pop({ type: 'error', body: self.error}); + }); + }; + } + + function AupResignController($scope, $uibModal, toaster) { + var self = this; + self.enabled = true; + + self.isMe = function () { + return self.userCtrl.isMe(); + }; + + self.openSignAUPModal = function() { + var modalInstance = $uibModal.open({ + templateUrl: '/resources/iam/apps/dashboard-app/templates/home/resignAup.html', + controller: ResignModalController, + controllerAs: 'resignModalCtrl', + resolve: {user: function() { return self.user; }} + }); + + modalInstance.result.then(function(msg) { + toaster.pop({type: 'success', body: msg}); + }, function () { + console.log('Re-sign AUP modal dismissed at: ' + new Date()); + }); + }; + } + + + angular.module('dashboardApp').component('aupResign', { + templateUrl: '/resources/iam/apps/dashboard-app/components/aup/aup.resign.component.html', + bindings: { + user: '<' + }, + controller: [ + '$rootScope', '$uibModal', 'toaster', 'AupService', AupResignController + ] + }); +})(); \ No newline at end of file diff --git a/iam-login-service/src/main/webapp/resources/iam/apps/dashboard-app/components/user/user.component.html b/iam-login-service/src/main/webapp/resources/iam/apps/dashboard-app/components/user/user.component.html index 0d3472f09..a1f566060 100644 --- a/iam-login-service/src/main/webapp/resources/iam/apps/dashboard-app/components/user/user.component.html +++ b/iam-login-service/src/main/webapp/resources/iam/apps/dashboard-app/components/user/user.component.html @@ -66,14 +66,7 @@

- + diff --git a/iam-login-service/src/main/webapp/resources/iam/apps/dashboard-app/components/user/user.component.js b/iam-login-service/src/main/webapp/resources/iam/apps/dashboard-app/components/user/user.component.js index a8724af99..6aa8ea1fd 100644 --- a/iam-login-service/src/main/webapp/resources/iam/apps/dashboard-app/components/user/user.component.js +++ b/iam-login-service/src/main/webapp/resources/iam/apps/dashboard-app/components/user/user.component.js @@ -16,30 +16,6 @@ (function () { 'use strict'; - function ResignAupController($scope, $uibModalInstance, AupService, user, toaster) { - var self = this; - self.enabled = true; - self.user = user; - - self.cancel = function() { - $uibModalInstance.close('Cancelled'); - }; - - self.submit = function() { - self.error = undefined; - self.enabled = false; - AupService.resignAup() - .then(function(res) { - $uibModalInstance.close('AUP signature re-signed succesfully'); - self.enabled = true; - }, function(res) { - self.error = res.data.error; - self.enabled = true; - toaster.pop({ type: 'error', body: self.error}); - }); - }; - } - function UserController( $timeout, $cookies, $rootScope, $uibModal, ModalService, scimFactory, UserService, Utils, toaster) { @@ -140,21 +116,6 @@ }) .catch(self.handleError); }; - - self.openSignAUPModal = function() { - var modalInstance = $uibModal.open({ - templateUrl: '/resources/iam/apps/dashboard-app/templates/home/resignAup.html', - controller: ResignAupController, - controllerAs: '$ctrl', - resolve: {user: function() { return self.user; }} - }); - - modalInstance.result.then(function(msg) { - toaster.pop({type: 'success', body: msg}); - }, function () { - console.log('Re-sign AUP modal dismissed at: ' + new Date()); - }); - }; } diff --git a/iam-login-service/src/main/webapp/resources/iam/apps/dashboard-app/services/aup.service.js b/iam-login-service/src/main/webapp/resources/iam/apps/dashboard-app/services/aup.service.js index 59ebdcd45..08f8d6096 100644 --- a/iam-login-service/src/main/webapp/resources/iam/apps/dashboard-app/services/aup.service.js +++ b/iam-login-service/src/main/webapp/resources/iam/apps/dashboard-app/services/aup.service.js @@ -83,7 +83,13 @@ } function resignAup() { - return $http.patch('/iam/aup/sign'); + return $http.post('/iam/aup/signature/').catch(function(res) { + if (res.status == 404) { + console.info("Account not found"); + return null; + } + return $q.reject(res); + }); } } })(); \ No newline at end of file diff --git a/iam-login-service/src/main/webapp/resources/iam/apps/dashboard-app/templates/home/resignAup.html b/iam-login-service/src/main/webapp/resources/iam/apps/dashboard-app/templates/home/resignAup.html index d5414eb95..e3aa4835e 100644 --- a/iam-login-service/src/main/webapp/resources/iam/apps/dashboard-app/templates/home/resignAup.html +++ b/iam-login-service/src/main/webapp/resources/iam/apps/dashboard-app/templates/home/resignAup.html @@ -21,21 +21,21 @@