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

feat(jans-auth-server): added flexible date formatter handler to AS (required by certification tools) #3600 #3601

Merged
merged 1 commit into from
Jan 11, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
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
4 changes: 2 additions & 2 deletions docs/admin/auth-server/tokens/openid-userinfo-token.md
Original file line number Diff line number Diff line change
Expand Up @@ -58,8 +58,8 @@ you should see the [Dynamic Scopes](../developer/scripts/dynamic-scope.md) inter

## Userinfo formatter

There is a configuration property `userInfoConfiguration` which has a default
value of `{'dateFormatterPattern': {'birthdate':'yyyy-MM-dd'}}`.
There is a configuration property `dateFormatterPatterns` which can be used for format date claims.
Example: `{'dateFormatterPatterns': {'userinfo':'yyyy-MM-dd'}}`.

## Language support

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -591,10 +591,8 @@ It returns all the information of the Jans Authorization server.
"ssaExpirationInDays": 30
},
"blockWebviewAuthorizationEnabled": false,
"userInfoConfiguration": {
"dateFormatterPattern": {
"birthdate": "yyyy-MM-dd"
}
"dateFormatterPatterns": {
"userinfo": "yyyy-MM-dd"
},
"fapi": false,
"allResponseTypesSupported": [
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ tags:
| corsConfigurationFilters | This list specifies the CORS configuration filters | [Details](#corsconfigurationfilters) |
| cssLocation | The location for CSS files | [Details](#csslocation) |
| customHeadersWithAuthorizationResponse | Choose whether to enable the custom response header parameter to return custom headers with the authorization response | [Details](#customheaderswithauthorizationresponse) |
| dateFormatterPattern | List of key value, e.g. 'birthdate: 'yyyy-MM-dd', etc. | [Details](#dateformatterpattern) |
| dateFormatterPatterns | List of key value, e.g. 'birthdate: 'yyyy-MM-dd', etc. | [Details](#dateformatterpattern) |
| dcrAuthorizationWithClientCredentials | Boolean value indicating if DCR authorization to be performed using client credentials | [Details](#dcrauthorizationwithclientcredentials) |
| dcrAuthorizationWithMTLS | Boolean value indicating if DCR authorization allowed with MTLS | [Details](#dcrauthorizationwithmtls) |
| dcrIssuers | List of DCR issuers | [Details](#dcrissuers) |
Expand Down Expand Up @@ -772,7 +772,7 @@ tags:
- Default value: None


### dateFormatterPattern
### dateFormatterPatterns

- Description: List of key value, e.g. 'birthdate: 'yyyy-MM-dd', etc.

Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
package io.jans.as.model.common;

/**
* @author Yuriy Z
*/
public enum CallerType {
COMMON,
AUTHORIZE,
USERINFO,
ID_TOKEN
}
Original file line number Diff line number Diff line change
Expand Up @@ -15,13 +15,9 @@
import io.jans.as.model.jwk.KeySelectionStrategy;
import io.jans.as.model.ssa.SsaConfiguration;
import io.jans.as.model.ssa.SsaValidationConfig;
import io.jans.as.model.userinfo.UserInfoConfiguration;
import io.jans.doc.annotation.DocProperty;

import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Set;
import java.util.*;

/**
* Represents the configuration JSON file.
Expand Down Expand Up @@ -820,8 +816,16 @@ public class AppConfiguration implements Configuration {
@DocProperty(description = "Enable/Disable block authorizations that originate from Webview (Mobile apps).", defaultValue = "false")
private Boolean blockWebviewAuthorizationEnabled = false;

@DocProperty(description = "UserInfo Configuration")
private UserInfoConfiguration userInfoConfiguration;
@DocProperty(description = "List of key value date formatters, e.g. 'userinfo: 'yyyy-MM-dd', etc.")
private Map<String, String> dateFormatterPatterns = new HashMap<>();

public Map<String, String> getDateFormatterPatterns() {
return dateFormatterPatterns;
}

public void setDateFormatterPatterns(Map<String, String> dateFormatterPatterns) {
this.dateFormatterPatterns = dateFormatterPatterns;
}

public List<SsaValidationConfig> getDcrSsaValidationConfigs() {
if (dcrSsaValidationConfigs == null) dcrSsaValidationConfigs = new ArrayList<>();
Expand Down Expand Up @@ -3121,12 +3125,4 @@ public Boolean getBlockWebviewAuthorizationEnabled() {
public void setBlockWebviewAuthorizationEnabled(Boolean blockWebviewAuthorizationEnabled) {
this.blockWebviewAuthorizationEnabled = blockWebviewAuthorizationEnabled;
}

public UserInfoConfiguration getUserInfoConfiguration() {
return userInfoConfiguration;
}

public void setUserInfoConfiguration(UserInfoConfiguration userInfoConfiguration) {
this.userInfoConfiguration = userInfoConfiguration;
}
}

This file was deleted.

6 changes: 2 additions & 4 deletions jans-auth-server/server/conf/jans-config.json
Original file line number Diff line number Diff line change
Expand Up @@ -451,9 +451,7 @@
}
},
"blockWebviewAuthorizationEnabled": false,
"userInfoConfiguration": {
"dateFormatterPattern": {
"birthdate": "yyyy-MM-dd"
}
"dateFormatterPatterns": {
"birthdate": "yyyy-MM-dd"
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
package io.jans.as.server.service.date;

import io.jans.as.model.common.CallerType;
import io.jans.as.model.configuration.AppConfiguration;
import jakarta.enterprise.context.ApplicationScoped;
import jakarta.inject.Inject;
import jakarta.inject.Named;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;

import java.io.Serializable;
import java.text.SimpleDateFormat;
import java.util.Date;
import java.util.Map;

/**
* @author Yuriy Z
*/
@ApplicationScoped
@Named
public class DateFormatterService {

@Inject
private Logger log;

@Inject
private AppConfiguration appConfiguration;

public Serializable formatClaim(Date date, CallerType callerType) {
return formatClaim(date, callerType.name().toLowerCase());
}

/**
*
* @param date date to format
* @param patternKey pattern key. It's by intention is not enum to allow arbitrary key (not "locked" by CallerType)
* @return formatter value
*/
public Serializable formatClaim(Date date, String patternKey) {
// key in map is string by intention to not "lock" it by CallerType
final Map<String, String> formatterMap = appConfiguration.getDateFormatterPatterns();

if (formatterMap.isEmpty()) {
return formatClaimFallback(date);
}

final String explicitFormatter = formatterMap.get(patternKey);
if (StringUtils.isNotBlank(explicitFormatter)) {
return new SimpleDateFormat(explicitFormatter).format(date);
}

final String commonFormatter = formatterMap.get(CallerType.COMMON.name().toLowerCase());
if (StringUtils.isNotBlank(commonFormatter)) {
return new SimpleDateFormat(commonFormatter).format(date);
}

return formatClaimFallback(date);
}

public Serializable formatClaimFallback(Date date) {
return date.getTime() / 1000;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -40,36 +40,32 @@
import io.jans.as.server.model.audit.Action;
import io.jans.as.server.model.audit.OAuth2AuditLog;
import io.jans.as.server.model.authorize.Claim;
import io.jans.as.server.model.common.AbstractToken;
import io.jans.as.server.model.common.AuthorizationGrant;
import io.jans.as.server.model.common.AuthorizationGrantList;
import io.jans.as.server.model.common.AuthorizationGrantType;
import io.jans.as.server.model.common.DefaultScope;
import io.jans.as.server.model.common.UnmodifiableAuthorizationGrant;
import io.jans.as.server.model.common.*;
import io.jans.as.server.model.userinfo.UserInfoParamsValidator;
import io.jans.as.server.service.ClientService;
import io.jans.as.server.service.ScopeService;
import io.jans.as.server.service.ServerCryptoProvider;
import io.jans.as.server.service.UserService;
import io.jans.as.server.service.date.DateFormatterService;
import io.jans.as.server.service.external.ExternalDynamicScopeService;
import io.jans.as.server.service.external.context.DynamicScopeExternalContext;
import io.jans.as.server.service.token.TokenService;
import io.jans.as.server.util.ServerUtil;
import io.jans.model.GluuAttribute;
import io.jans.orm.exception.EntryPersistenceException;
import org.json.JSONObject;
import org.slf4j.Logger;

import jakarta.inject.Inject;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.ws.rs.Path;
import jakarta.ws.rs.core.MediaType;
import jakarta.ws.rs.core.Response;
import jakarta.ws.rs.core.SecurityContext;
import org.json.JSONObject;
import org.slf4j.Logger;

import java.io.Serializable;
import java.nio.charset.StandardCharsets;
import java.security.PublicKey;
import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.*;

/**
Expand Down Expand Up @@ -120,6 +116,9 @@ public class UserInfoRestWebServiceImpl implements UserInfoRestWebService {
@Inject
private TokenService tokenService;

@Inject
private DateFormatterService dateFormatterService;

@Override
public Response requestUserInfoGet(String accessToken, String authorization, HttpServletRequest request, SecurityContext securityContext) {
return requestUserInfo(accessToken, authorization, request, securityContext);
Expand Down Expand Up @@ -362,13 +361,8 @@ public String getJSonResponse(User user, AuthorizationGrant authorizationGrant,
} else if (value instanceof Boolean) {
jsonWebResponse.getClaims().setClaim(key, (Boolean) value);
} else if (value instanceof Date) {
Date casteValue = (Date) value;
Optional<String> optionalValue = getFormattedValueFromUserInfoConfiguration(key, casteValue);
if (optionalValue.isPresent()) {
jsonWebResponse.getClaims().setClaim(key, optionalValue.get());
} else {
jsonWebResponse.getClaims().setClaim(key, casteValue.getTime() / 1000);
}
Serializable formattedValue = dateFormatterService.formatClaim((Date) value, key);
jsonWebResponse.getClaims().setClaimObject(key, formattedValue, true);
} else {
jsonWebResponse.getClaims().setClaim(key, String.valueOf(value));
}
Expand Down Expand Up @@ -451,17 +445,4 @@ public boolean validateRequesteClaim(GluuAttribute gluuAttribute, String[] clien

return false;
}

private Optional<String> getFormattedValueFromUserInfoConfiguration(String key, Date value) {
String patternValue = appConfiguration.getUserInfoConfiguration().getDateFormatterPattern().get(key);
if (patternValue != null) {
try {
SimpleDateFormat simpleDateFormat = new SimpleDateFormat(patternValue);
return Optional.of(simpleDateFormat.format(value));
} catch (Exception e) {
log.warn("Error during SimpleDateFormat instance: {}", e.getMessage());
}
}
return Optional.empty();
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,87 @@
package io.jans.as.server.service.date;

import io.jans.as.model.common.CallerType;
import io.jans.as.model.configuration.AppConfiguration;
import org.mockito.InjectMocks;
import org.mockito.Mock;
import org.mockito.Spy;
import org.mockito.testng.MockitoTestNGListener;
import org.slf4j.Logger;
import org.testng.annotations.Listeners;
import org.testng.annotations.Test;

import java.io.Serializable;
import java.util.Calendar;
import java.util.Date;
import java.util.HashMap;
import java.util.Map;

import static org.mockito.Mockito.when;
import static org.testng.Assert.assertEquals;

/**
* @author Yuriy Z
*/
@Listeners(MockitoTestNGListener.class)
public class DateFormatterServiceTest {

@InjectMocks
@Spy
private DateFormatterService dateFormatterService;

@Mock
private Logger log;

@Mock
private AppConfiguration appConfiguration;

@Test
public void formatClaim_whenNotFormatter_shouldFallback() {
Date date = new Date(10000);

final Serializable formattedValue = dateFormatterService.formatClaim(date, CallerType.USERINFO);
assertEquals(10L, formattedValue);
}

@Test
public void formatClaim_whenExplicitClaimFormatterIsSet_shouldFormatByFormatter() {
Calendar calendar = Calendar.getInstance();
calendar.set(2023, Calendar.JANUARY, 11);

Map<String, String> map = new HashMap<>();
map.put("birthdate", "yyyy-MM-dd");

when(appConfiguration.getDateFormatterPatterns()).thenReturn(map);

final Serializable formattedValue = dateFormatterService.formatClaim(calendar.getTime(), "birthdate");
assertEquals(formattedValue, "2023-01-11");
}

@Test
public void formatClaim_whenUserInfoFormatterIsSet_shouldFormatByFormatter() {
Calendar calendar = Calendar.getInstance();
calendar.set(2023, Calendar.JANUARY, 11);

Map<String, String> map = new HashMap<>();
map.put("userinfo", "yyyy-MM-dd");

when(appConfiguration.getDateFormatterPatterns()).thenReturn(map);

final Serializable formattedValue = dateFormatterService.formatClaim(calendar.getTime(), CallerType.USERINFO);
assertEquals(formattedValue, "2023-01-11");
}

@Test
public void formatClaim_whenCommonFormatterIsSet_shouldFormatByFormatter() {
Calendar calendar = Calendar.getInstance();
calendar.set(2023, Calendar.JANUARY, 11);

Map<String, String> map = new HashMap<>();
map.put("common", "yyyy-MM-dd");

when(appConfiguration.getDateFormatterPatterns()).thenReturn(map);

final Serializable formattedValue = dateFormatterService.formatClaim(calendar.getTime(), CallerType.USERINFO);
assertEquals(formattedValue, "2023-01-11");
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -395,9 +395,7 @@
"redirectUrisRegexEnabled": false,
"useHighestLevelScriptIfAcrScriptNotFound": true,
"blockWebviewAuthorizationEnabled": false,
"userInfoConfiguration": {
"dateFormatterPattern": {
"birthdate": "yyyy-MM-dd"
}
"dateFormatterPatterns": {
"birthdate": "yyyy-MM-dd"
}
}
Loading