Skip to content

Commit

Permalink
feat(jans-auth-server): added flexible date formatter handler to AS (…
Browse files Browse the repository at this point in the history
…required by certification tools) #3600
  • Loading branch information
yuriyz committed Jan 11, 2023
1 parent e3bd1e8 commit 9367aaf
Show file tree
Hide file tree
Showing 12 changed files with 195 additions and 89 deletions.
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

0 comments on commit 9367aaf

Please sign in to comment.