|
5 | 5 | import java.sql.Timestamp;
|
6 | 6 | import java.time.Instant;
|
7 | 7 | import java.time.temporal.ChronoUnit;
|
8 |
| -import java.util.List; |
| 8 | +import java.util.HashSet; |
| 9 | +import java.util.Map; |
9 | 10 | import java.util.Set;
|
10 | 11 |
|
11 | 12 | import javax.inject.Inject;
|
12 | 13 | import javax.inject.Named;
|
13 | 14 |
|
| 15 | +import org.apache.commons.mail.EmailException; |
| 16 | +import org.apache.commons.mail.SimpleEmail; |
| 17 | +import org.sonatype.nexus.common.app.BaseUrlHolder; |
| 18 | +import org.sonatype.nexus.email.EmailManager; |
14 | 19 | import org.sonatype.nexus.logging.task.TaskLogging;
|
15 | 20 | import org.sonatype.nexus.scheduling.Cancelable;
|
16 | 21 | import org.sonatype.nexus.scheduling.TaskSupport;
|
17 |
| -import org.sonatype.nexus.security.user.UserManager; |
| 22 | +import org.sonatype.nexus.security.SecuritySystem; |
| 23 | +import org.sonatype.nexus.security.user.User; |
18 | 24 | import org.sonatype.nexus.security.user.UserNotFoundException;
|
19 | 25 |
|
20 | 26 | import com.github.tumbl3w33d.h2.OAuth2ProxyLoginRecordStore;
|
| 27 | +import com.github.tumbl3w33d.h2.OAuth2ProxyTokenInfoStore; |
21 | 28 | import com.github.tumbl3w33d.users.OAuth2ProxyUserManager;
|
22 | 29 | import com.github.tumbl3w33d.users.db.OAuth2ProxyLoginRecord;
|
| 30 | +import com.github.tumbl3w33d.users.db.OAuth2ProxyTokenInfo; |
23 | 31 |
|
24 | 32 | @Named
|
25 | 33 | @TaskLogging(NEXUS_LOG_ONLY)
|
26 | 34 | public class OAuth2ProxyApiTokenInvalidateTask extends TaskSupport implements Cancelable {
|
27 | 35 |
|
28 | 36 | private final OAuth2ProxyLoginRecordStore loginRecordStore;
|
29 |
| - |
| 37 | + private final OAuth2ProxyTokenInfoStore tokenInfoStore; |
30 | 38 | private final OAuth2ProxyUserManager userManager;
|
| 39 | + private final SecuritySystem securitySystem; |
| 40 | + private final EmailManager mailManager; |
31 | 41 |
|
32 | 42 | @Inject
|
33 | 43 | public OAuth2ProxyApiTokenInvalidateTask(@Named OAuth2ProxyLoginRecordStore loginRecordStore,
|
34 |
| - final List<UserManager> userManagers, final OAuth2ProxyUserManager userManager) { |
35 |
| - |
| 44 | + @Named OAuth2ProxyTokenInfoStore tokenInfoStore, @Named OAuth2ProxyUserManager userManager, |
| 45 | + SecuritySystem securitySystem, EmailManager mailManager) { |
36 | 46 | this.loginRecordStore = loginRecordStore;
|
| 47 | + this.tokenInfoStore = tokenInfoStore; |
37 | 48 | this.userManager = userManager;
|
38 |
| - } |
39 |
| - |
40 |
| - private void resetApiToken(String userId) { |
41 |
| - try { |
42 |
| - userManager.changePassword(userId, OAuth2ProxyRealm.generateSecureRandomString(32)); |
43 |
| - log.debug("API token reset for user {} succeeded", userId); |
44 |
| - } catch (UserNotFoundException e) { |
45 |
| - log.error("Unable to reset API token of user {} - {}", userId, e); |
46 |
| - } |
| 49 | + this.securitySystem = securitySystem; |
| 50 | + this.mailManager = mailManager; |
47 | 51 | }
|
48 | 52 |
|
49 | 53 | @Override
|
50 | 54 | protected Void execute() throws Exception {
|
| 55 | + Map<String, OAuth2ProxyLoginRecord> loginRecords = loginRecordStore.getAllLoginRecords(); |
| 56 | + Map<String, OAuth2ProxyTokenInfo> tokenInfos = tokenInfoStore.getAllTokenInfos(); |
51 | 57 |
|
52 |
| - Set<OAuth2ProxyLoginRecord> loginRecords = loginRecordStore.getAllLoginRecords(); |
53 |
| - |
54 |
| - if (loginRecords.isEmpty()) { |
55 |
| - log.debug("No login records found, nothing to do"); |
| 58 | + if (loginRecords.isEmpty() && tokenInfos.isEmpty()) { |
| 59 | + log.debug("No records found, nothing to do"); |
56 | 60 | return null;
|
57 | 61 | }
|
58 | 62 |
|
59 |
| - for (OAuth2ProxyLoginRecord loginRecord : loginRecords) { |
60 |
| - String userId = loginRecord.getId(); |
61 |
| - Timestamp lastLoginDate = loginRecord.getLastLogin(); |
62 |
| - |
63 |
| - Instant lastLoginInstant = lastLoginDate.toInstant(); |
64 |
| - Instant nowInstant = Instant.now(); |
| 63 | + int configuredIdleExpiration = getConfiguration().getInteger( |
| 64 | + OAuth2ProxyApiTokenInvalidateTaskDescriptor.CONFIG_IDLE_EXPIRY, |
| 65 | + OAuth2ProxyApiTokenInvalidateTaskDescriptor.CONFIG_IDLE_EXPIRY_DEFAULT); |
| 66 | + |
| 67 | + int configuredMaxTokenAge = getConfiguration().getInteger( |
| 68 | + OAuth2ProxyApiTokenInvalidateTaskDescriptor.CONFIG_AGE, |
| 69 | + OAuth2ProxyApiTokenInvalidateTaskDescriptor.CONFIG_AGE_DEFAULT); |
| 70 | + |
| 71 | + boolean notify = getConfiguration().getBoolean(OAuth2ProxyApiTokenInvalidateTaskDescriptor.NOTIFY, |
| 72 | + OAuth2ProxyApiTokenInvalidateTaskDescriptor.NOTIFY_DEFAULT); |
| 73 | + |
| 74 | + Set<String> userIds = new HashSet<>(loginRecords.size()); |
| 75 | + userIds.addAll(loginRecords.keySet()); |
| 76 | + userIds.addAll(tokenInfos.keySet()); |
| 77 | + for (String userId : userIds) { |
| 78 | + if ("admin".equals(userId)) { |
| 79 | + // never reset the admin "token" as it would overwrite the password, possibly locking people out of nexus |
| 80 | + // when the task would run before OIDC setup is completed |
| 81 | + continue; |
| 82 | + } |
65 | 83 |
|
66 |
| - log.debug("Last known login for {} was {}", userId, |
67 |
| - OAuth2ProxyRealm.formatDateString(lastLoginDate)); |
| 84 | + if (isUserIdleTimeExpired(userId, loginRecords.get(userId), configuredIdleExpiration) |
| 85 | + || isTokenLifespanExpired(userId, tokenInfos.get(userId), configuredMaxTokenAge)) { |
| 86 | + resetApiToken(userId, notify); |
| 87 | + log.info("API token of user {} has been reset", userId); |
| 88 | + } |
68 | 89 |
|
69 |
| - long timePassed = ChronoUnit.DAYS.between(lastLoginInstant, nowInstant); |
| 90 | + } |
| 91 | + return null; |
| 92 | + } |
70 | 93 |
|
71 |
| - int configuredDuration = getConfiguration() |
72 |
| - .getInteger(OAuth2ProxyApiTokenInvalidateTaskDescriptor.CONFIG_EXPIRY, 1); |
| 94 | + private boolean isUserIdleTimeExpired(String userId, OAuth2ProxyLoginRecord loginRecord, int configuredIdleTime) { |
| 95 | + if (configuredIdleTime <= 0) { |
| 96 | + return false; |
| 97 | + } |
73 | 98 |
|
74 |
| - log.debug("Time passed since login: {} - configured maximum: {}", timePassed, |
75 |
| - configuredDuration); |
| 99 | + Timestamp lastLoginDate = loginRecord.getLastLogin(); |
| 100 | + log.debug("Last known login for {} was {}", userId, OAuth2ProxyRealm.formatDateString(lastLoginDate)); |
| 101 | + long timePassed = ChronoUnit.DAYS.between(lastLoginDate.toInstant(), Instant.now()); |
| 102 | + log.debug("Time passed since login: {} - configured maximum: {}", timePassed, configuredIdleTime); |
| 103 | + if (timePassed >= configuredIdleTime) { |
| 104 | + log.debug("Idle time expired for {}", userId); |
| 105 | + return true; |
| 106 | + } |
| 107 | + return false; |
| 108 | + } |
76 | 109 |
|
77 |
| - if (timePassed >= configuredDuration) { |
78 |
| - resetApiToken(userId); |
79 |
| - log.info("Reset api token of user {} because they did not show up for a while", |
80 |
| - userId); |
81 |
| - } |
| 110 | + private boolean isTokenLifespanExpired(String userId, OAuth2ProxyTokenInfo tokenInfo, int configuredMaxTokenAge) { |
| 111 | + if (configuredMaxTokenAge <= 0) { |
| 112 | + return false; |
82 | 113 | }
|
83 | 114 |
|
84 |
| - return null; |
| 115 | + Timestamp tokenCreationDate = tokenInfo.getTokenCreation(); |
| 116 | + log.debug("API token for {} was created at {}", userId, OAuth2ProxyRealm.formatDateString(tokenCreationDate)); |
| 117 | + long timePassed = ChronoUnit.DAYS.between(tokenCreationDate.toInstant(), Instant.now()); |
| 118 | + log.debug("Time passed since token creation: {} - configured maximum: {}", timePassed, configuredMaxTokenAge); |
| 119 | + if (timePassed >= configuredMaxTokenAge) { |
| 120 | + log.debug("Token lifespan expired for user {}", userId); |
| 121 | + return true; |
| 122 | + } |
| 123 | + return false; |
85 | 124 | }
|
86 | 125 |
|
87 | 126 | @Override
|
88 | 127 | public String getMessage() {
|
89 | 128 | return "Invalidate OAuth2 Proxy API tokens of users who did not show up for a while";
|
90 | 129 | }
|
91 | 130 |
|
| 131 | + private void resetApiToken(String userId, boolean notify) { |
| 132 | + try { |
| 133 | + securitySystem.changePassword(userId, OAuth2ProxyRealm.generateSecureRandomString(32)); |
| 134 | + log.debug("API token reset for user {} succeeded", userId); |
| 135 | + if (notify) { |
| 136 | + sendMail(userId); |
| 137 | + } |
| 138 | + } catch (UserNotFoundException e) { |
| 139 | + log.error("Unable to reset API token of user {}", userId); |
| 140 | + log.debug("Unable to reset API token of user {}", userId, e); |
| 141 | + } |
| 142 | + } |
| 143 | + |
| 144 | + private void sendMail(String userId) throws UserNotFoundException { |
| 145 | + if (mailManager.getConfiguration().isEnabled()) { |
| 146 | + User user = userManager.getUser(userId); |
| 147 | + String to = user.getEmailAddress(); |
| 148 | + try { |
| 149 | + SimpleEmail mail = new SimpleEmail(); |
| 150 | + mail.addTo(to); |
| 151 | + if (BaseUrlHolder.isSet()) { |
| 152 | + mail.setMsg("Your OAuth2 Proxy API Token on " + BaseUrlHolder.get() |
| 153 | + + " has been invalidated because of inactivity or expired token lifespan"); |
| 154 | + } else { |
| 155 | + mail.setMsg( |
| 156 | + "Your OAuth2 Proxy API Token has been invalidated because of inactivity or expired token lifespan"); |
| 157 | + } |
| 158 | + mailManager.send(mail); |
| 159 | + } catch (EmailException e) { |
| 160 | + log.warn("Failed to send notification email about oauth2 API token reset to user " + user.getName()); |
| 161 | + log.debug("Failed to send notification email", e); |
| 162 | + } |
| 163 | + } else { |
| 164 | + log.warn("Sending token invalidation notifications is enabled, but no mail server is configured in Nexus"); |
| 165 | + } |
| 166 | + } |
| 167 | + |
92 | 168 | }
|
0 commit comments