Skip to content

Commit 24d2292

Browse files
committed
notify on expiring and expired tokens
1 parent 5d18201 commit 24d2292

File tree

9 files changed

+66
-3
lines changed

9 files changed

+66
-3
lines changed

core/src/main/java/hudson/model/userproperty/UserPropertyCategorySecurityAction.java

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,7 +34,9 @@
3434
import java.util.Collection;
3535
import java.util.Collections;
3636
import java.util.List;
37+
import jenkins.management.Badge;
3738
import jenkins.model.Jenkins;
39+
import jenkins.security.ApiTokenProperty;
3840
import org.jenkinsci.Symbol;
3941
import org.kohsuke.accmod.Restricted;
4042
import org.kohsuke.accmod.restrictions.NoExternalUse;
@@ -64,6 +66,14 @@ public String getUrlName() {
6466
return UserProperty.allByCategoryClass(UserPropertyCategory.Security.class);
6567
}
6668

69+
public Badge getBadge() {
70+
ApiTokenProperty apiTokenProperty = getTargetUser().getProperty(ApiTokenProperty.class);
71+
if (apiTokenProperty != null) {
72+
return apiTokenProperty.getBadge();
73+
}
74+
return null;
75+
}
76+
6777
/**
6878
* Inject the outer class configuration page into the sidenav and the request routing of the user
6979
*/

core/src/main/java/jenkins/model/navigation/UserAction.java

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,8 @@
3232
import hudson.model.User;
3333
import java.util.ArrayList;
3434
import java.util.List;
35+
import jenkins.management.Badge;
36+
import jenkins.security.ApiTokenProperty;
3537
import org.kohsuke.accmod.Restricted;
3638
import org.kohsuke.accmod.restrictions.NoExternalUse;
3739

@@ -80,6 +82,20 @@ public User getUser() {
8082
return User.current();
8183
}
8284

85+
@Override
86+
public Badge getBadge() {
87+
User current = User.current();
88+
89+
if (User.current() == null) {
90+
return null;
91+
}
92+
ApiTokenProperty apiTokenProperty = current.getProperty(ApiTokenProperty.class);
93+
if (apiTokenProperty != null) {
94+
return apiTokenProperty.getBadge();
95+
}
96+
return null;
97+
}
98+
8399
@Override
84100
public boolean isPrimaryAction() {
85101
return true;

core/src/main/java/jenkins/security/ApiTokenProperty.java

Lines changed: 24 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -52,6 +52,7 @@
5252
import java.util.logging.Level;
5353
import java.util.logging.Logger;
5454
import java.util.stream.Collectors;
55+
import jenkins.management.Badge;
5556
import jenkins.model.Jenkins;
5657
import jenkins.security.apitoken.ApiTokenPropertyConfiguration;
5758
import jenkins.security.apitoken.ApiTokenStats;
@@ -258,6 +259,7 @@ public static class TokenInfoAndStats {
258259
public final long numDaysUse;
259260
public final String expirationDate;
260261
public final boolean expired;
262+
public final boolean aboutToExpire;
261263

262264
public TokenInfoAndStats(@NonNull ApiTokenStore.HashedToken token, @NonNull ApiTokenStats.SingleTokenStats stats) {
263265
this.uuid = token.getUuid();
@@ -266,6 +268,7 @@ public TokenInfoAndStats(@NonNull ApiTokenStore.HashedToken token, @NonNull ApiT
266268
this.numDaysCreation = token.getNumDaysCreation();
267269
this.isLegacy = token.isLegacy();
268270
this.expired = token.isExpired();
271+
this.aboutToExpire = token.isAboutToExpire();
269272

270273
LocalDate expirationDate = token.getExpirationDate();
271274
if (expirationDate == null) {
@@ -795,4 +798,25 @@ public HttpResponse doRevokeAllExcept(@AncestorInPath User u,
795798
@Deprecated
796799
@Restricted(NoExternalUse.class)
797800
public static final HMACConfidentialKey API_KEY_SEED = new HMACConfidentialKey(ApiTokenProperty.class, "seed", 16);
801+
802+
@Restricted(NoExternalUse.class)
803+
public Badge getBadge() {
804+
long expiringTokenCount = getTokenList().stream().filter(t -> t.aboutToExpire && !t.expired).count();
805+
long expiredTokenCount = getTokenList().stream().filter(t -> t.expired).count();
806+
StringBuilder tooltip = new StringBuilder();
807+
if (expiringTokenCount > 0) {
808+
tooltip.append(jenkins.model.navigation.Messages.UserAction_aboutToExpireTokens(expiringTokenCount));
809+
}
810+
if (expiredTokenCount > 0) {
811+
if (expiringTokenCount > 0) {
812+
tooltip.append("\n");
813+
}
814+
tooltip.append(jenkins.model.navigation.Messages.UserAction_expiredTokens(expiredTokenCount));
815+
}
816+
if (expiredTokenCount + expiringTokenCount > 0) {
817+
return new Badge(Long.toString(expiringTokenCount + expiredTokenCount), tooltip.toString(), Badge.Severity.WARNING);
818+
} else {
819+
return null;
820+
}
821+
}
798822
}

core/src/main/java/jenkins/security/apitoken/ApiTokenStore.java

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -433,6 +433,14 @@ public void rename(String newName) {
433433
this.name = newName;
434434
}
435435

436+
public boolean isAboutToExpire() {
437+
if (expirationDate == null) {
438+
return false;
439+
}
440+
LocalDate warnLimit = LocalDate.now().plusDays(7);
441+
return expirationDate.isBefore(warnLimit);
442+
}
443+
436444
public boolean isExpired() {
437445
LocalDate now = LocalDate.now();
438446
LocalDate expiration = Objects.requireNonNullElseGet(expirationDate, LocalDate::now);
Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
UserAction.expiredTokens={0} expired tokens.
2+
UserAction.aboutToExpireTokens={0} tokens about to expire.

core/src/main/resources/jenkins/model/navigation/UserAction/jumplist.jelly

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -15,7 +15,8 @@
1515
<j:forEach var="action" items="${it.actions}">
1616
<dd:item icon="${action.iconFileName}"
1717
text="${action.displayName}"
18-
href="${rootURL}/${it.user.url}/${action.urlName}" />
18+
href="${rootURL}/${it.user.url}/${action.urlName}"
19+
badge="${action.badge}"/>
1920
</j:forEach>
2021

2122
<dd:separator />

core/src/main/resources/jenkins/security/ApiTokenProperty/config.jelly

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -175,7 +175,7 @@ THE SOFTWARE.
175175
</span>
176176
</j:when>
177177
<j:otherwise>
178-
<span>
178+
<span class="${token.aboutToExpire ? 'warning' : ''}">
179179
${%tokenExpiresOn(token.expirationDate)}
180180
</span>
181181
</j:otherwise>

core/src/main/resources/jenkins/security/ApiTokenProperty/resources.js

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -109,6 +109,7 @@ function appendTokenToTable(data) {
109109
apiTokenRow.querySelector(".token-name").innerText = data.tokenName;
110110
tokenShowButton.dataset.tokenValue = data.tokenValue;
111111
tokenShowButton.dataset.title = data.tokenName;
112+
tokenShowButton.dataset.expirationDate = data.expirationDate;
112113
tokenShowButton.classList.remove("jenkins-hidden");
113114
tokenList.appendChild(apiTokenRow);
114115
adjustTokenEmptyListMessage();
@@ -333,6 +334,7 @@ Behaviour.specify(
333334
showToken(
334335
button.dataset.title,
335336
button.dataset.tokenValue,
337+
button.dataset.expirationDate,
336338
button.dataset.buttonDone,
337339
);
338340
};

core/src/main/resources/lib/hudson/actions.jelly

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -46,7 +46,7 @@ THE SOFTWARE.
4646
</l:task>
4747
</j:when>
4848
<j:otherwise>
49-
<l:task icon="${icon}" title="${action.displayName}" href="${h.getActionUrl(it.url,action)}" contextMenu="${h.isContextMenuVisible(action)}"/>
49+
<l:task icon="${icon}" title="${action.displayName}" href="${h.getActionUrl(it.url,action)}" contextMenu="${h.isContextMenuVisible(action)}" badge="${action.badge}"/>
5050
</j:otherwise>
5151
</j:choose>
5252
</j:if>

0 commit comments

Comments
 (0)