Skip to content

Commit

Permalink
Add last_used property to clients (#675)
Browse files Browse the repository at this point in the history
* Add table CLIENT_LAST_USED
* Add last used column on dashboard clients table
* Add TOKEN event to IamAuditApplicationEvent and publish AccessTokenIssuedEvent and AccessTokenRefreshedEvent
* Hide column on dashboard when tracking disabled
* Enable the tracking by default
  • Loading branch information
darcato authored and enricovianello committed Apr 14, 2024
1 parent d52941f commit ddcc4c2
Show file tree
Hide file tree
Showing 19 changed files with 401 additions and 12 deletions.
Original file line number Diff line number Diff line change
Expand Up @@ -105,7 +105,7 @@ public ClientDetailsEntity entityFromClientManagementRequest(RegisteredClientDTO

public RegisteredClientDTO registeredClientDtoFromEntity(ClientDetailsEntity entity) {
RegisteredClientDTO clientDTO = new RegisteredClientDTO();

clientDTO.setClientId(entity.getClientId());
clientDTO.setClientSecret(entity.getClientSecret());
clientDTO.setClientName(entity.getClientName());
Expand All @@ -127,6 +127,9 @@ public RegisteredClientDTO registeredClientDtoFromEntity(ClientDetailsEntity ent
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());
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -46,5 +46,4 @@ Optional<ClientDetailsEntity> findClientByClientIdAndAccount(String clientId,
ClientDetailsEntity updateClient(ClientDetailsEntity client);

void deleteClient(ClientDetailsEntity client);

}
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@
*/
package it.infn.mw.iam.api.common.client;

import java.time.LocalDate;
import java.util.Date;
import java.util.Set;

Expand All @@ -29,6 +30,7 @@

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;
Expand Down Expand Up @@ -234,6 +236,11 @@ public class RegisteredClientDTO {
ClientViews.DynamicRegistration.class})
private Date createdAt;

@JsonView({ClientViews.Limited.class, ClientViews.Full.class, ClientViews.ClientManagement.class,
ClientViews.DynamicRegistration.class})
@JsonFormat(shape = JsonFormat.Shape.STRING)
private LocalDate lastUsed;

@JsonView({ClientViews.Full.class, ClientViews.ClientManagement.class,
ClientViews.DynamicRegistration.class})
@Size(max = 2048, groups = {OnClientCreation.class, OnClientUpdate.class})
Expand Down Expand Up @@ -472,6 +479,14 @@ public void setCreatedAt(Date createdAt) {
this.createdAt = createdAt;
}

public LocalDate getLastUsed() {
return lastUsed;
}

public void setLastUsed(LocalDate lastUsed) {
this.lastUsed = lastUsed;
}

public String getJwk() {
return jwk;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -42,7 +42,8 @@ public enum IamEventCategory {
SCOPE_POLICY,
AUP,
MEMBERSHIP,
CLIENT
CLIENT,
TOKEN
}

private static final long serialVersionUID = -6276169409979227109L;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
/**
* 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.tokens;

import org.mitre.oauth2.model.OAuth2AccessTokenEntity;


public class AccessTokenIssuedEvent extends TokenEvent {

private static final long serialVersionUID = 1L;

public AccessTokenIssuedEvent(Object source, OAuth2AccessTokenEntity token) {
super(source, token, "Access token issued");
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
/**
* 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.tokens;

import org.mitre.oauth2.model.OAuth2AccessTokenEntity;


public class AccessTokenRefreshedEvent extends TokenEvent {

private static final long serialVersionUID = 1L;

public AccessTokenRefreshedEvent(Object source, OAuth2AccessTokenEntity token) {
super(source, token, "Access token refreshed");
}

}
Original file line number Diff line number Diff line change
@@ -0,0 +1,51 @@
/**
* 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.tokens;

import java.util.Date;
import java.util.Set;

import org.mitre.oauth2.model.OAuth2AccessTokenEntity;

import it.infn.mw.iam.audit.events.IamAuditApplicationEvent;

public abstract class TokenEvent extends IamAuditApplicationEvent {
private static final long serialVersionUID = 1L;
private final Date expiration;
private final String clientId;
private final Set<String> scopes;

public TokenEvent(Object source, OAuth2AccessTokenEntity token, String message) {
super(IamEventCategory.TOKEN, source, message);
this.expiration = token.getExpiration();
this.clientId = token.getClient().getClientId();
this.scopes = token.getScope();
}

public Date getExpiration() {
return expiration;
}

public String getClientId() {
return clientId;
}

public Set<String> getScopes() {
return scopes;
}


}
Original file line number Diff line number Diff line change
Expand Up @@ -92,15 +92,13 @@ public void setPassword(String password) {

}


public static class ExternalConnectivityProbeProperties {

private boolean enabled = true;

private String endpoint = "https://www.google.com";
private int timeoutInSecs = 10;


public boolean isEnabled() {
return enabled;
}
Expand Down Expand Up @@ -292,7 +290,6 @@ public void setAllowCompleteVerificationUri(Boolean allowCompleteVerificationUri
this.allowCompleteVerificationUri = allowCompleteVerificationUri;
}


}

public static class JWKProperties {
Expand Down Expand Up @@ -540,6 +537,18 @@ public void setLocation(String location) {
}
}

public static class ClientProperties {
private boolean trackLastUsed;

public boolean isTrackLastUsed() {
return trackLastUsed;
}

public void setTrackLastUsed(boolean trackLastUsed) {
this.trackLastUsed = trackLastUsed;
}
}

private String host;

private String issuer;
Expand Down Expand Up @@ -588,14 +597,14 @@ public void setLocation(String location) {

private CustomizationProperties customization = new CustomizationProperties();

private VersionedStaticResourcesProperties versionedStaticResources =
new VersionedStaticResourcesProperties();
private VersionedStaticResourcesProperties versionedStaticResources = new VersionedStaticResourcesProperties();

private ExternalConnectivityProbeProperties externalConnectivityProbe =
new ExternalConnectivityProbeProperties();
private ExternalConnectivityProbeProperties externalConnectivityProbe = new ExternalConnectivityProbeProperties();

private AccountLinkingProperties accountLinking = new AccountLinkingProperties();

private ClientProperties client = new ClientProperties();

public String getBaseUrl() {
return baseUrl;
}
Expand Down Expand Up @@ -814,4 +823,12 @@ public void setAccountLinking(AccountLinkingProperties accountLinking) {
this.accountLinking = accountLinking;
}

public void setClient(ClientProperties client) {
this.client = client;
}

public ClientProperties getClient(){
return client;
}

}
Original file line number Diff line number Diff line change
Expand Up @@ -18,17 +18,26 @@
import java.util.Date;
import java.util.Set;

import java.time.LocalDate;
import org.mitre.oauth2.model.ClientDetailsEntity;
import org.mitre.oauth2.model.ClientLastUsedEntity;
import org.mitre.oauth2.model.OAuth2AccessTokenEntity;
import org.mitre.oauth2.model.OAuth2RefreshTokenEntity;
import org.mitre.oauth2.service.impl.DefaultOAuth2ProviderTokenService;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.context.ApplicationEventPublisher;
import org.springframework.context.annotation.Primary;
import org.springframework.security.oauth2.provider.OAuth2Authentication;
import org.springframework.security.oauth2.provider.TokenRequest;
import org.springframework.stereotype.Service;

import com.google.common.collect.Sets;

import it.infn.mw.iam.audit.events.tokens.AccessTokenIssuedEvent;
import it.infn.mw.iam.audit.events.tokens.AccessTokenRefreshedEvent;
import it.infn.mw.iam.config.IamProperties;
import it.infn.mw.iam.persistence.repository.IamOAuthAccessTokenRepository;
import it.infn.mw.iam.persistence.repository.IamOAuthRefreshTokenRepository;

Expand All @@ -40,14 +49,19 @@ public class IamTokenService extends DefaultOAuth2ProviderTokenService {

private final IamOAuthAccessTokenRepository accessTokenRepo;
private final IamOAuthRefreshTokenRepository refreshTokenRepo;
private final ApplicationEventPublisher eventPublisher;
private final IamProperties iamProperties;


@Autowired
public IamTokenService(IamOAuthAccessTokenRepository atRepo,
IamOAuthRefreshTokenRepository rtRepo) {
IamOAuthRefreshTokenRepository rtRepo, ApplicationEventPublisher publisher,
IamProperties iamProperties) {

this.accessTokenRepo = atRepo;
this.refreshTokenRepo = rtRepo;
this.eventPublisher = publisher;
this.iamProperties = iamProperties;
}

@Override
Expand Down Expand Up @@ -76,4 +90,48 @@ public void revokeRefreshToken(OAuth2RefreshTokenEntity refreshToken) {
refreshTokenRepo.delete(refreshToken);
}

@Override
@SuppressWarnings("deprecation")
public OAuth2AccessTokenEntity createAccessToken(OAuth2Authentication authentication) {

OAuth2AccessTokenEntity token = super.createAccessToken(authentication);

if (iamProperties.getClient().isTrackLastUsed()) {
updateClientLastUsed(token);
}

eventPublisher.publishEvent(new AccessTokenIssuedEvent(this, token));
return token;
}

@Override
@SuppressWarnings("deprecation")
public OAuth2AccessTokenEntity refreshAccessToken(String refreshTokenValue,
TokenRequest authRequest) {

OAuth2AccessTokenEntity token = super.refreshAccessToken(refreshTokenValue, authRequest);

if (iamProperties.getClient().isTrackLastUsed()) {
updateClientLastUsed(token);
}

eventPublisher.publishEvent(new AccessTokenRefreshedEvent(this, token));
return token;
}

private void updateClientLastUsed(OAuth2AccessTokenEntity token) {
ClientDetailsEntity client = token.getClient();
ClientLastUsedEntity clientLastUsed = client.getClientLastUsed();
LocalDate now = LocalDate.now();

if (clientLastUsed == null) {
clientLastUsed = new ClientLastUsedEntity(client, now);
client.setClientLastUsed(clientLastUsed);
} else {
LocalDate lastUsed = clientLastUsed.getLastUsed();
if (lastUsed.isBefore(now)) {
clientLastUsed.setLastUsed(now);
}
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -42,6 +42,7 @@ public class IamViewInfoInterceptor implements HandlerInterceptor {
public static final String RCAUTH_ENABLED_KEY = "iamRcauthEnabled";
public static final String RESOURCES_PATH_KEY = "resourcesPrefix";
public static final String CLIENT_DEFAULTS_PROPERTIES_KEY = "clientDefaultsProperties";
public static final String CLIENT_TRACK_LAST_USED_KEY = "clientTrackLastUsed";

@Value("${iam.version}")
String iamVersion;
Expand Down Expand Up @@ -83,6 +84,8 @@ public boolean preHandle(HttpServletRequest request, HttpServletResponse respons
request.setAttribute(RCAUTH_ENABLED_KEY, rcAuthProperties.isEnabled());

request.setAttribute(CLIENT_DEFAULTS_PROPERTIES_KEY, clientRegistrationProperties.getClientDefaults());

request.setAttribute(CLIENT_TRACK_LAST_USED_KEY, iamProperties.getClient().isTrackLastUsed());

if (iamProperties.getVersionedStaticResources().isEnableVersioning()) {
request.setAttribute(RESOURCES_PATH_KEY, String.format("/resources/%s", gitCommitId));
Expand Down
3 changes: 3 additions & 0 deletions iam-login-service/src/main/resources/application.yml
Original file line number Diff line number Diff line change
Expand Up @@ -225,6 +225,9 @@ client-registration:
default-id-token-validity-seconds: ${DEFAULT_ID_TOKEN_VALIDITY_SECONDS:600}
default-refresh-token-validity-seconds: ${DEFAULT_REFRESH_TOKEN_VALIDITY_SECONDS:2592000}

client:
track-last-used: ${IAM_CLIENT_TRACK_LAST_USED:true}

management:
health:
redis:
Expand Down
4 changes: 4 additions & 0 deletions iam-login-service/src/main/webapp/WEB-INF/tags/iamHeader.tag
Original file line number Diff line number Diff line change
Expand Up @@ -111,4 +111,8 @@ function getAccessTokenValiditySeconds() {
function getRefreshTokenValiditySeconds() {
return ${clientDefaultsProperties.defaultRefreshTokenValiditySeconds};
}
function getClientTrackLastUsed() {
return ${clientTrackLastUsed};
}
</script>
Loading

0 comments on commit ddcc4c2

Please sign in to comment.