Skip to content

Commit a2bc756

Browse files
committed
Fixes #698
1 parent 8c99b87 commit a2bc756

File tree

10 files changed

+140
-61
lines changed

10 files changed

+140
-61
lines changed

client/src/tabs/UserRoleAudits.scss

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -5,27 +5,27 @@
55
thead {
66
th {
77
&.userEmail {
8-
width: 20%;
8+
width: 30%;
99
}
1010

1111
&.roleName {
1212
width: 30%;
1313
}
1414

1515
&.action {
16-
width: 12%;
16+
width: 10%;
1717
}
1818

1919
&.authority {
20-
width: 12%;
20+
width: 10%;
2121
}
2222

2323
&.createdAt {
24-
width: 12%;
24+
width: 10%;
2525
}
2626

2727
&.endDate {
28-
width: 12%;
28+
width: 10%;
2929
}
3030

3131
}

server/src/main/java/invite/api/SystemController.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -64,10 +64,10 @@ public SystemController(ResourceCleaner resourceCleaner,
6464
}
6565

6666
@GetMapping("/cron/cleanup")
67-
public ResponseEntity<Map<String, List<? extends Serializable>>> cronCleanup(@Parameter(hidden = true) User user) {
67+
public ResponseEntity<Map<String, Object>> cronCleanup(@Parameter(hidden = true) User user) {
6868
LOG.debug(String.format("/cron/cleanup for user %s", user.getEduPersonPrincipalName()));
6969
UserPermissions.assertSuperUser(user);
70-
Map<String, List<? extends Serializable>> body = resourceCleaner.doClean();
70+
Map<String, Object> body = resourceCleaner.doClean();
7171
return ResponseEntity.ok(body);
7272
}
7373

server/src/main/java/invite/cron/ResourceCleaner.java

Lines changed: 16 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@
99
import invite.provision.ProvisioningService;
1010
import invite.provision.scim.OperationType;
1111
import invite.repository.UserRepository;
12+
import invite.repository.UserRoleAuditRepository;
1213
import invite.repository.UserRoleRepository;
1314
import org.apache.commons.logging.Log;
1415
import org.apache.commons.logging.LogFactory;
@@ -32,41 +33,47 @@ public class ResourceCleaner extends AbstractNodeLeader {
3233
private static final Log LOG = LogFactory.getLog(ResourceCleaner.class);
3334

3435
private final UserRepository userRepository;
36+
private final UserRoleAuditRepository userRoleAuditRepository;
3537
private final ProvisioningService provisioningService;
3638
private final UserRoleRepository userRoleRepository;
3739
private final UserRoleAuditService userRoleAuditService;
3840
private final int lastActivityDurationDays;
41+
private final int purgeAuditLogDays;
3942

4043

4144
@Autowired
4245
public ResourceCleaner(UserRepository userRepository,
4346
UserRoleRepository userRoleRepository,
4447
ProvisioningService provisioningService,
4548
DataSource dataSource,
49+
UserRoleAuditRepository userRoleAuditRepository,
4650
UserRoleAuditService userRoleAuditService,
47-
@Value("${cron.last-activity-duration-days}") int lastActivityDurationDays) {
51+
@Value("${cron.last-activity-duration-days}") int lastActivityDurationDays,
52+
@Value("${cron.purge-audit-log-days}") int purgeAuditLogDays) {
4853
super(LOCK_NAME, dataSource);
4954
this.userRepository = userRepository;
5055
this.userRoleRepository = userRoleRepository;
56+
this.userRoleAuditRepository = userRoleAuditRepository;
5157
this.userRoleAuditService = userRoleAuditService;
5258
this.lastActivityDurationDays = lastActivityDurationDays;
5359
this.provisioningService = provisioningService;
54-
}
60+
this.purgeAuditLogDays = purgeAuditLogDays; }
5561

5662
@Scheduled(cron = "${cron.user-cleaner-expression}")
5763
@Transactional
5864
public void clean() {
5965
super.perform("ResourceCleaner#clean", this::doClean);
6066
}
6167

62-
public Map<String, List<? extends Serializable>> doClean() {
68+
public Map<String, Object> doClean() {
6369
List<User> users = cleanNonActiveUsers();
6470
List<User> orphans = cleanOrphanedUser();
6571
List<UserRole> userRoles = cleanUserRoles();
6672
return Map.of(
6773
"DeletedNonActiveUsers", users,
6874
"DeletedOrphanUsers", orphans,
69-
"DeletedExpiredUserRoles", userRoles
75+
"DeletedExpiredUserRoles", userRoles,
76+
"DeletedUserRoleAudits", cleanUserRoleAudit()
7077
);
7178
}
7279

@@ -117,4 +124,9 @@ private List<UserRole> cleanUserRoles() {
117124
return userRoles;
118125
}
119126

127+
private int cleanUserRoleAudit() {
128+
Instant past = Instant.now().minus(Period.ofDays(purgeAuditLogDays));
129+
return userRoleAuditRepository.deleteByCreatedAtBefore(past);
130+
}
131+
120132
}

server/src/main/java/invite/model/UserRoleAudit.java

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -62,14 +62,14 @@ public UserRoleAudit(ActionType action, UserRole userRole, String createdBy) {
6262
this.createdBy = createdBy;
6363
}
6464

65-
public UserRoleAudit(Role role, User user) {
65+
public UserRoleAudit(Role role, User user, Instant createdAt) {
6666
this.roleId = role.getId();
6767
this.roleName = role.getName();
6868
this.userId = user.getId();
6969
this.userEmail = user.getEmail();
7070
this.action = ActionType.ADD;
7171
this.authority = Authority.GUEST;
72-
this.createdAt = Instant.now();
72+
this.createdAt = createdAt;
7373
this.createdBy = "test";
7474
}
7575

server/src/main/java/invite/repository/UserRoleAuditRepository.java

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,12 @@
44
import org.springframework.data.domain.Page;
55
import org.springframework.data.domain.Pageable;
66
import org.springframework.data.jpa.repository.JpaRepository;
7+
import org.springframework.data.jpa.repository.Modifying;
78
import org.springframework.data.jpa.repository.Query;
89
import org.springframework.data.repository.query.Param;
910
import org.springframework.stereotype.Repository;
1011

12+
import java.time.Instant;
1113
import java.util.List;
1214

1315
@Repository
@@ -38,4 +40,6 @@ Page<UserRoleAudit> findAllByEmailLikeAndRoleIdIn(@Param("email") String email,
3840
countQuery = "SELECT COUNT(u) FROM user_roles_audit u WHERE u.roleId IN :roleIds"
3941
)
4042
Page<UserRoleAudit> findAllByRoleIdIn(@Param("roleIds") List<Long> roleIds, Pageable pageable);
43+
44+
int deleteByCreatedAtBefore(Instant cutoff);
4145
}

server/src/main/resources/application.yml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -78,7 +78,6 @@ crypto:
7878
private-key-location: classpath:/private_key_pkcs8.pem
7979

8080
cron:
81-
node-cron-job-responsible: true
8281
user-cleaner-expression: "0 0/30 * * * *"
8382
last-activity-duration-days: 1000
8483
role-expiration-notifier-expression: "0 0/30 * * * *"
@@ -87,6 +86,8 @@ cron:
8786
metadata-resolver-initial-delay-milliseconds: 1
8887
metadata-resolver-fixed-rate-milliseconds: 86_400_000
8988
metadata-resolver-url: "classpath:/metadata/idps-metadata.xml"
89+
# A value of 0 means no logs will be deleted
90+
purge-audit-log-days: 365
9091

9192
myconext:
9293
uri: "https://login.test2.eduid.nl/myconext/api/invite/provision-eduid"

server/src/test/java/invite/AbstractTest.java

Lines changed: 72 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -1,15 +1,5 @@
11
package invite;
22

3-
import invite.config.HashGenerator;
4-
import invite.crm.CRMContact;
5-
import invite.crm.CRMOrganisation;
6-
import invite.crm.CRMRole;
7-
import invite.eduid.EduIDProvision;
8-
import invite.manage.EntityType;
9-
import invite.manage.LocalManage;
10-
import invite.model.*;
11-
import invite.provision.scim.GroupURN;
12-
import invite.repository.*;
133
import com.fasterxml.jackson.core.JsonProcessingException;
144
import com.fasterxml.jackson.core.type.TypeReference;
155
import com.fasterxml.jackson.databind.ObjectMapper;
@@ -23,6 +13,39 @@
2313
import com.nimbusds.jose.jwk.RSAKey;
2414
import com.nimbusds.jwt.JWTClaimsSet;
2515
import com.nimbusds.jwt.SignedJWT;
16+
import invite.config.HashGenerator;
17+
import invite.crm.CRMContact;
18+
import invite.crm.CRMOrganisation;
19+
import invite.crm.CRMRole;
20+
import invite.eduid.EduIDProvision;
21+
import invite.manage.EntityType;
22+
import invite.manage.LocalManage;
23+
import invite.model.APIToken;
24+
import invite.model.Application;
25+
import invite.model.ApplicationUsage;
26+
import invite.model.Authority;
27+
import invite.model.Invitation;
28+
import invite.model.InvitationRole;
29+
import invite.model.Language;
30+
import invite.model.Organisation;
31+
import invite.model.RemoteProvisionedUser;
32+
import invite.model.RequestedAuthnContext;
33+
import invite.model.Role;
34+
import invite.model.User;
35+
import invite.model.UserRole;
36+
import invite.model.UserRoleAudit;
37+
import invite.provision.scim.GroupURN;
38+
import invite.repository.APITokenRepository;
39+
import invite.repository.ApplicationRepository;
40+
import invite.repository.ApplicationUsageRepository;
41+
import invite.repository.InvitationRepository;
42+
import invite.repository.OrganisationRepository;
43+
import invite.repository.RemoteProvisionedGroupRepository;
44+
import invite.repository.RemoteProvisionedUserRepository;
45+
import invite.repository.RoleRepository;
46+
import invite.repository.UserRepository;
47+
import invite.repository.UserRoleAuditRepository;
48+
import invite.repository.UserRoleRepository;
2649
import io.restassured.RestAssured;
2750
import io.restassured.common.mapper.TypeRef;
2851
import io.restassured.config.ObjectMapperConfig;
@@ -54,14 +77,27 @@
5477
import java.io.IOException;
5578
import java.net.URLDecoder;
5679
import java.nio.charset.StandardCharsets;
57-
import java.security.*;
80+
import java.security.KeyPair;
81+
import java.security.KeyPairGenerator;
82+
import java.security.NoSuchAlgorithmException;
83+
import java.security.NoSuchProviderException;
84+
import java.security.Security;
5885
import java.security.interfaces.RSAPrivateKey;
5986
import java.security.interfaces.RSAPublicKey;
6087
import java.text.SimpleDateFormat;
6188
import java.time.Clock;
6289
import java.time.Instant;
6390
import java.time.temporal.ChronoUnit;
64-
import java.util.*;
91+
import java.util.ArrayList;
92+
import java.util.Arrays;
93+
import java.util.Collections;
94+
import java.util.Date;
95+
import java.util.HashMap;
96+
import java.util.List;
97+
import java.util.Map;
98+
import java.util.Optional;
99+
import java.util.Set;
100+
import java.util.UUID;
65101
import java.util.function.Consumer;
66102
import java.util.function.UnaryOperator;
67103
import java.util.stream.Collectors;
@@ -142,6 +178,9 @@ public abstract class AbstractTest {
142178
@Autowired
143179
protected InvitationRepository invitationRepository;
144180

181+
@Autowired
182+
protected UserRoleAuditRepository userRoleAuditRepository;
183+
145184
@Autowired
146185
protected RemoteProvisionedGroupRepository remoteProvisionedGroupRepository;
147186

@@ -639,7 +678,7 @@ private void doSeed() {
639678
institutionAdmin.setOrganizationGUID(ORGANISATION_GUID);
640679

641680
Organisation organisation = new Organisation(
642-
CRM_ORGANIZATION_ID,"SURF","SURF"
681+
CRM_ORGANIZATION_ID, "SURF", "SURF"
643682
);
644683
doSave(organisationRepository, organisation);
645684

@@ -774,6 +813,26 @@ private void doSeed() {
774813
doSave(apiTokenRepository, apiToken, superUserApiToken, legacyApiToken, userApiToken);
775814
}
776815

816+
protected void seedUserRoleAudits(Instant createdAt) {
817+
this.userRoleAuditRepository.deleteAllInBatch();
818+
Role network = this.roleRepository.findByName("Network").get();
819+
Role research = this.roleRepository.findByName("Research").get();
820+
Role mail = this.roleRepository.findByName("Mail").get();
821+
822+
//paul.doe@example.com
823+
User inviter = this.userRepository.findBySubIgnoreCase(INVITER_SUB).get();
824+
//ann.doe@example.com
825+
User guest = this.userRepository.findBySubIgnoreCase(GUEST_SUB).get();
826+
Instant now = Instant.now();
827+
UserRoleAudit auditNetworkInviter = new UserRoleAudit(network, inviter, now);
828+
UserRoleAudit auditResearchInviter = new UserRoleAudit(research, inviter, now);
829+
UserRoleAudit auditResearchGuest = new UserRoleAudit(research, guest, now);
830+
UserRoleAudit auditMailGuest = new UserRoleAudit(mail, guest, now);
831+
832+
doSave(userRoleAuditRepository, auditNetworkInviter, auditResearchInviter, auditResearchGuest, auditMailGuest);
833+
}
834+
835+
777836
@SafeVarargs
778837
protected final <M> void doSave(JpaRepository<M, Long> repository, M... entities) {
779838
repository.saveAll(Arrays.asList(entities));

server/src/test/java/invite/api/UserRoleAuditControllerTest.java

Lines changed: 4 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@
33
import invite.AbstractTest;
44
import invite.AccessCookieFilter;
55
import invite.DefaultPage;
6+
import invite.cron.ResourceCleaner;
67
import invite.model.Role;
78
import invite.model.User;
89
import invite.model.UserRoleAudit;
@@ -14,6 +15,8 @@
1415
import org.springframework.beans.factory.annotation.Autowired;
1516
import org.springframework.http.HttpStatus;
1617

18+
import java.time.Instant;
19+
import java.time.temporal.ChronoUnit;
1720
import java.util.List;
1821
import java.util.Map;
1922

@@ -22,13 +25,10 @@
2225

2326
class UserRoleAuditControllerTest extends AbstractTest {
2427

25-
@Autowired
26-
protected UserRoleAuditRepository userRoleAuditRepository;
27-
2828
@BeforeEach
2929
protected void beforeEach() throws Exception {
3030
super.beforeEach();
31-
this.seedUserRoleAudits();
31+
this.seedUserRoleAudits(Instant.now());
3232
}
3333

3434
@Test
@@ -181,23 +181,4 @@ void fetchRolesSuperUser() throws Exception {
181181
assertEquals(6, res.size());
182182
}
183183

184-
private void seedUserRoleAudits() {
185-
this.userRoleAuditRepository.deleteAllInBatch();
186-
Role network = this.roleRepository.findByName("Network").get();
187-
Role research = this.roleRepository.findByName("Research").get();
188-
Role mail = this.roleRepository.findByName("Mail").get();
189-
190-
//paul.doe@example.com
191-
User inviter = this.userRepository.findBySubIgnoreCase(INVITER_SUB).get();
192-
//ann.doe@example.com
193-
User guest = this.userRepository.findBySubIgnoreCase(GUEST_SUB).get();
194-
195-
UserRoleAudit auditNetworkInviter = new UserRoleAudit(network, inviter);
196-
UserRoleAudit auditResearchInviter = new UserRoleAudit(research, inviter);
197-
UserRoleAudit auditResearchGuest = new UserRoleAudit(research, guest);
198-
UserRoleAudit auditMailGuest = new UserRoleAudit(mail, guest);
199-
doSave(userRoleAuditRepository, auditNetworkInviter, auditResearchInviter, auditResearchGuest, auditMailGuest);
200-
}
201-
202-
203184
}

0 commit comments

Comments
 (0)