Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

VO members can re-sign the AUP at any time #757

Merged
merged 25 commits into from
May 29, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
25 commits
Select commit Hold shift + click to select a range
9451f09
Add ability to disable client
garaimanoj Feb 26, 2024
95a8c19
Make fields available for limited client view
garaimanoj Apr 15, 2024
ba51133
Throw exception on update of suspended client
garaimanoj Apr 22, 2024
5062702
Handle client suspended exception
garaimanoj Apr 22, 2024
4a62227
Add AUP re-sign button
garaimanoj Apr 24, 2024
a9ea662
Add AUP re-sign modal and controller
garaimanoj Apr 24, 2024
615d548
Add sign AUP function
garaimanoj Apr 24, 2024
5672a3d
Add message on cancelation
garaimanoj Apr 26, 2024
4ab275a
Add new event for re-sign AUP
garaimanoj Apr 29, 2024
20f30bc
Use update signature method to update sign time
garaimanoj Apr 29, 2024
0568a1d
Test user re-sign AUP API
garaimanoj Apr 30, 2024
a4c8071
Test exception in absence of user AUP
garaimanoj Apr 30, 2024
73bc405
Merge branch 'develop' into issue-705
garaimanoj May 9, 2024
2f73024
Remove duplicate code using responseFromOptional method
garaimanoj May 10, 2024
66ce06b
Return ScimUser to match interface design
garaimanoj May 10, 2024
56be86c
Use different endpoint to enable and disable client
garaimanoj May 10, 2024
21779fc
Fix test cases
garaimanoj May 10, 2024
912a963
Fix typo
garaimanoj May 15, 2024
26b53a9
Check user updating suspended client
garaimanoj May 15, 2024
8c980d8
Remove unused imports
garaimanoj May 15, 2024
da4ea6f
Merge branch 'issue-705' into issue-736-re-sign-AUP
garaimanoj May 15, 2024
20a283d
Create sub-component for re-sign AUP
garaimanoj May 21, 2024
ef46cf9
Set current timestamp as default for status_changed_on column
garaimanoj May 24, 2024
5033d84
Merge branch 'issue-705' into issue-736-re-sign-AUP
garaimanoj May 24, 2024
c81c450
Make re-sign visible only to respective user
garaimanoj May 24, 2024
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -25,11 +26,9 @@
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;
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;
Expand Down Expand Up @@ -65,18 +64,13 @@ public ScimListResponse<ScimUser> findAccountByLabel(String labelName, String la
@Override
public ScimListResponse<ScimUser> findAccountByEmail(String emailAddress) {
Optional<IamAccount> account = repo.findByEmail(emailAddress);

ScimListResponseBuilder<ScimUser> builder = ScimListResponse.builder();
account.ifPresent(a -> builder.singleResource(converter.dtoFromEntity(a)));
return builder.build();
return responseFromOptional(account, converter);
}

@Override
public ScimListResponse<ScimUser> findAccountByUsername(String username) {
Optional<IamAccount> account = repo.findByUsername(username);
ScimListResponseBuilder<ScimUser> builder = ScimListResponse.builder();
account.ifPresent(a -> builder.singleResource(converter.dtoFromEntity(a)));
return builder.build();
return responseFromOptional(account, converter);
}

@Override
Expand Down Expand Up @@ -115,9 +109,7 @@ private Supplier<IllegalArgumentException> groupNotFoundError(String groupNameOr
@Override
public ScimListResponse<ScimUser> findAccountByCertificateSubject(String certSubject) {
Optional<IamAccount> account = repo.findByCertificateSubject(certSubject);
ScimListResponseBuilder<ScimUser> builder = ScimListResponse.builder();
account.ifPresent(a -> builder.singleResource(converter.dtoFromEntity(a)));
return builder.build();
return responseFromOptional(account, converter);
}

@Override
Expand All @@ -143,4 +135,9 @@ public ScimListResponse<ScimUser> findAccountByGroupUuidWithFilter(String groupU
Page<IamAccount> results = repo.findByGroupUuidWithFilter(group.getUuid(), filter, pageable);
return responseFromPage(results, converter, pageable);
}

public ScimListResponse<ScimUser> findAccountByUuid(String uuid) {
Optional<IamAccount> account = repo.findByUuid(uuid);
return responseFromOptional(account, converter);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -25,10 +25,7 @@
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 it.infn.mw.iam.api.common.ListResponseDTO;
import it.infn.mw.iam.api.common.form.PaginatedRequestWithFilterForm;
Expand All @@ -44,6 +41,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 =
Expand Down Expand Up @@ -121,4 +119,9 @@ public ListResponseDTO<ScimUser> findNotInGroup(@PathVariable String groupUuid,
}
}

@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 ListResponseDTO<ScimUser> findByUuid(@PathVariable String accountUuid) {
return service.findAccountByUuid(accountUuid);
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -45,5 +45,6 @@ ScimListResponse<ScimUser> findAccountByGroupUuidWithFilter(String groupUuid, St

ScimListResponse<ScimUser> findAccountNotInGroupWithFilter(String groupUuid, String filter,
Pageable pageable);


ScimListResponse<ScimUser> findAccountByUuid(String uuid);
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
/**
* 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.error;

public class ClientSuspended extends RuntimeException {

private static final long serialVersionUID = 1L;

public ClientSuspended(String message) {
super(message);
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand All @@ -44,6 +45,7 @@

import com.fasterxml.jackson.annotation.JsonView;

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;
Expand All @@ -53,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)
Expand All @@ -61,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
Expand Down Expand Up @@ -140,6 +145,20 @@ public RegisteredClientDTO updateClient(@PathVariable String clientId,
return managementService.updateClient(clientId, client);
}

@PatchMapping("/{clientId}/enable")
@PreAuthorize("#iam.hasScope('iam:admin.write') or #iam.hasDashboardRole('ROLE_ADMIN')")
public void enableClient(@PathVariable String clientId) {
Optional<IamAccount> 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<IamAccount> account = accountUtils.getAuthenticatedUserAccount();
account.ifPresent(a -> managementService.updateClientStatus(clientId, false, a.getUuid()));
}

@PostMapping("/{clientId}/secret")
@ResponseStatus(CREATED)
@PreAuthorize("#iam.hasScope('iam:admin.write') or #iam.hasDashboardRole('ROLE_ADMIN')")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,8 @@ RegisteredClientDTO updateClient(@NotBlank String clientId,

void deleteClientByClientId(@NotBlank String clientId);

void updateClientStatus(String clientId, boolean status, String userId);

ListResponseDTO<ScimUser> getClientOwners(@NotBlank String clientId, @NotNull Pageable pageable);

void assignClientOwner(@NotBlank String clientId, @IamAccountId String accountId);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -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);
Expand All @@ -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)
Expand All @@ -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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@

import com.fasterxml.jackson.annotation.JsonView;

import it.infn.mw.iam.api.client.error.ClientSuspended;
import it.infn.mw.iam.api.client.error.InvalidClientRegistrationRequest;
import it.infn.mw.iam.api.client.error.NoSuchClient;
import it.infn.mw.iam.api.client.registration.service.ClientRegistrationService;
Expand Down Expand Up @@ -119,6 +120,12 @@ public ErrorDTO noSuchClient(HttpServletRequest req, Exception ex) {
return ErrorDTO.fromString(ex.getMessage());
}

@ResponseStatus(value = HttpStatus.FORBIDDEN)
@ExceptionHandler(ClientSuspended.class)
public ErrorDTO clientSuspended(HttpServletRequest req, Exception ex) {
return ErrorDTO.fromString(ex.getMessage());
}

@ResponseStatus(value = HttpStatus.BAD_REQUEST)
@ExceptionHandler(InvalidClientRegistrationRequest.class)
public ErrorDTO invalidRequest(HttpServletRequest req, Exception ex) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@

import it.infn.mw.iam.api.account.AccountUtils;
import it.infn.mw.iam.api.client.error.InvalidClientRegistrationRequest;
import it.infn.mw.iam.api.client.error.ClientSuspended;
import it.infn.mw.iam.api.client.registration.validation.OnDynamicClientRegistration;
import it.infn.mw.iam.api.client.registration.validation.OnDynamicClientUpdate;
import it.infn.mw.iam.api.client.service.ClientConverter;
Expand Down Expand Up @@ -320,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,
Expand All @@ -330,6 +340,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);
Expand Down Expand Up @@ -395,9 +406,10 @@ public RegisteredClientDTO updateClient(String clientId, RegisteredClientDTO req
ClientDetailsEntity oldClient =
lookupClient(clientId, authentication).orElseThrow(clientNotFound(clientId));

checkUserUpdatingSuspendedClient(authentication, oldClient);
checkAllowedGrantTypesOnUpdate(request, authentication, oldClient);
cleanupRequestedScopesOnUpdate(request, authentication, oldClient);

ClientDetailsEntity newClient = converter.entityFromRegistrationRequest(request);
newClient.setId(oldClient.getId());
newClient.setClientSecret(oldClient.getClientSecret());
Expand All @@ -410,6 +422,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);

Expand All @@ -421,8 +434,7 @@ public RegisteredClientDTO updateClient(String clientId, RegisteredClientDTO req
eventPublisher.publishEvent(new ClientRegistrationAccessTokenRotatedEvent(this, savedClient));
response.setRegistrationAccessToken(t);
});

return response;
return response;
}

@Override
Expand Down
Loading
Loading