diff --git a/README.md b/README.md index a103c05a..0ea970a4 100644 --- a/README.md +++ b/README.md @@ -12,25 +12,29 @@ Quick links: | --- | --- | --- | --- | ## Install -Current version - 1.2.0 + +The library supports the following Java environments: +- Java 8 (or higher) + +Current version - 1.3.0 You can find the changes for each version in the [change log](https://github.com/AzureAD/microsoft-authentication-library-for-java/blob/master/changelog.txt). You can get the msal4j package through Maven or Gradle. ### Maven - +Find [the latest package in the Maven repository](https://mvnrepository.com/artifact/com.microsoft.azure/msal4j). ``` com.microsoft.azure msal4j - 1.2.0 + 1.3.0 ``` ### Gradle ``` -compile group: 'com.microsoft.azure', name: 'msal4j', version: '1.2.0' +compile group: 'com.microsoft.azure', name: 'msal4j', version: '1.3.0' ``` ## Usage diff --git a/changelog.txt b/changelog.txt index b99f5ee8..edeab1d3 100644 --- a/changelog.txt +++ b/changelog.txt @@ -1,3 +1,9 @@ +Version 1.3.0 +============= +- Added option to pass in AAD instance discovery data +- Removed runtime dependency on commons-codec.commons-codec, org.apache.commons.commons-text, org.apache.httpcomponents.httpclient, com.google.code.gson +- Added runtime dependency on com.fasterxml.jackson.databind + Version 1.2.0 ============= - Added support for ADFS 2019 diff --git a/lombok.config b/lombok.config new file mode 100644 index 00000000..f902278d --- /dev/null +++ b/lombok.config @@ -0,0 +1 @@ +lombok.anyConstructor.addConstructorProperties=true \ No newline at end of file diff --git a/pom.xml b/pom.xml index cf5940a9..40bb2d4b 100644 --- a/pom.xml +++ b/pom.xml @@ -3,7 +3,7 @@ 4.0.0 com.microsoft.azure msal4j - 1.2.0 + 1.3.0 jar msal4j @@ -38,33 +38,31 @@ oauth2-oidc-sdk 6.5 - - com.google.code.gson - gson - 2.8.5 - org.slf4j slf4j-api - 1.7.21 - - - commons-codec - commons-codec - 1.11 - - - org.apache.commons - commons-text - 1.7 + 1.7.28 org.projectlombok lombok 1.18.6 + provided + + com.fasterxml.jackson.core + jackson-databind + 2.10.1 + + + + org.apache.commons + commons-text + 1.7 + test + org.testng testng @@ -99,6 +97,7 @@ org.apache.httpcomponents httpclient 4.5.9 + test com.microsoft.azure diff --git a/src/integrationtest/java/labapi/App.java b/src/integrationtest/java/labapi/App.java index 21d8a1a0..be3830b8 100644 --- a/src/integrationtest/java/labapi/App.java +++ b/src/integrationtest/java/labapi/App.java @@ -3,28 +3,28 @@ package labapi; -import com.google.gson.annotations.SerializedName; +import com.fasterxml.jackson.annotation.JsonProperty; import lombok.Getter; @Getter public class App { - @SerializedName("appType") + @JsonProperty("appType") String appType; - @SerializedName("appName") + @JsonProperty("appName") String appName; - @SerializedName("appId") + @JsonProperty("appId") String appId; - @SerializedName("redirectUri") + @JsonProperty("redirectUri") String redirectUri; - @SerializedName("authority") + @JsonProperty("authority") String authority; - @SerializedName("labName") + @JsonProperty("labName") String labName; } diff --git a/src/integrationtest/java/labapi/Lab.java b/src/integrationtest/java/labapi/Lab.java index bd365bac..bae558e8 100644 --- a/src/integrationtest/java/labapi/Lab.java +++ b/src/integrationtest/java/labapi/Lab.java @@ -3,26 +3,26 @@ package labapi; -import com.google.gson.annotations.SerializedName; +import com.fasterxml.jackson.annotation.JsonProperty; import lombok.Getter; @Getter public class Lab { - @SerializedName("labName") + @JsonProperty("labName") String labName; - @SerializedName("domain") + @JsonProperty("domain") String domain; - @SerializedName("tenantId") + @JsonProperty("tenantId") String tenantId; - @SerializedName("federationProvider") + @JsonProperty("federationProvider") String federationProvider; - @SerializedName("azureEnvironment") + @JsonProperty("azureEnvironment") String azureEnvironment; - @SerializedName("authority") + @JsonProperty("authority") String authority; } diff --git a/src/integrationtest/java/labapi/LabService.java b/src/integrationtest/java/labapi/LabService.java index 029fa18f..57898afd 100644 --- a/src/integrationtest/java/labapi/LabService.java +++ b/src/integrationtest/java/labapi/LabService.java @@ -3,7 +3,9 @@ package labapi; -import com.google.gson.Gson; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.ObjectMapper; import com.microsoft.aad.msal4j.*; import java.net.MalformedURLException; @@ -16,6 +18,17 @@ public class LabService { static ConfidentialClientApplication labApp; + static ObjectMapper mapper = new ObjectMapper() + .configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); + + static T convertJsonToObject(final String json, final Class clazz) { + try { + return mapper.readValue(json, clazz); + } catch (JsonProcessingException e) { + throw new RuntimeException("JsonProcessingException: " + e.getMessage(), e); + } + } + static void initLabApp() throws MalformedURLException { KeyVaultSecretsProvider keyVaultSecretsProvider = new KeyVaultSecretsProvider(); @@ -44,7 +57,7 @@ User getUser(UserQueryParameters query){ String result = HttpClientHelper.sendRequestToLab( LabConstants.LAB_USER_ENDPOINT, queryMap, getLabAccessToken()); - User[] users = new Gson().fromJson(result, User[].class); + User[] users = convertJsonToObject(result, User[].class); User user = users[0]; user.setPassword(getUserSecret(user.getLabName())); if (query.parameters.containsKey(UserQueryParameters.FEDERATION_PROVIDER)) { @@ -62,7 +75,7 @@ public static App getApp(String appId){ try { String result = HttpClientHelper.sendRequestToLab( LabConstants.LAB_APP_ENDPOINT, appId, getLabAccessToken()); - App[] apps = new Gson().fromJson(result, App[].class); + App[] apps = convertJsonToObject(result, App[].class); return apps[0]; } catch (Exception ex) { throw new RuntimeException("Error getting app from lab: " + ex.getMessage()); @@ -74,7 +87,7 @@ public static Lab getLab(String labId) { try { result = HttpClientHelper.sendRequestToLab( LabConstants.LAB_LAB_ENDPOINT, labId, getLabAccessToken()); - Lab[] labs = new Gson().fromJson(result, Lab[].class); + Lab[] labs = convertJsonToObject(result, Lab[].class); return labs[0]; } catch (Exception ex) { throw new RuntimeException("Error getting lab from lab: " + ex.getMessage()); @@ -89,7 +102,7 @@ private String getUserSecret(String labName){ result = HttpClientHelper.sendRequestToLab( LabConstants.LAB_USER_SECRET_ENDPOINT, queryMap, getLabAccessToken()); - return new Gson().fromJson(result, UserSecret.class).value; + return convertJsonToObject(result, UserSecret.class).value; } catch (Exception ex) { throw new RuntimeException("Error getting user secret from lab: " + ex.getMessage()); } diff --git a/src/integrationtest/java/labapi/User.java b/src/integrationtest/java/labapi/User.java index d9decfa9..ead4be82 100644 --- a/src/integrationtest/java/labapi/User.java +++ b/src/integrationtest/java/labapi/User.java @@ -3,53 +3,53 @@ package labapi; -import com.google.gson.annotations.SerializedName; +import com.fasterxml.jackson.annotation.JsonProperty; import lombok.Getter; import lombok.Setter; @Getter public class User { - @SerializedName("appId") + @JsonProperty("appId") private String appId; - @SerializedName("objectId") + @JsonProperty("objectId") private String objectId; - @SerializedName("userType") + @JsonProperty("userType") private String userType; - @SerializedName("displayName") + @JsonProperty("displayName") private String displayName; - @SerializedName("licenses") + @JsonProperty("licenses") private String licenses; - @SerializedName("upn") + @JsonProperty("upn") private String upn; - @SerializedName("mfa") + @JsonProperty("mfa") private String mfa; - @SerializedName("protectionPolicy") + @JsonProperty("protectionPolicy") private String protectionPolicy; - @SerializedName("homeDomain") + @JsonProperty("homeDomain") private String homeDomain; - @SerializedName("homeUPN") + @JsonProperty("homeUPN") private String homeUPN; - @SerializedName("b2cProvider") + @JsonProperty("b2cProvider") private String b2cProvider; - @SerializedName("labName") + @JsonProperty("labName") private String labName; - @SerializedName("lastUpdatedBy") + @JsonProperty("lastUpdatedBy") private String lastUpdatedBy; - @SerializedName("lastUpdatedDate") + @JsonProperty("lastUpdatedDate") private String lastUpdatedDate; @Setter diff --git a/src/integrationtest/java/labapi/UserSecret.java b/src/integrationtest/java/labapi/UserSecret.java index 0a74f561..e33f47e2 100644 --- a/src/integrationtest/java/labapi/UserSecret.java +++ b/src/integrationtest/java/labapi/UserSecret.java @@ -3,13 +3,13 @@ package labapi; -import com.google.gson.annotations.SerializedName; +import com.fasterxml.jackson.annotation.JsonProperty; public class UserSecret { - @SerializedName("secret") + @JsonProperty("secret") String secret; - @SerializedName("value") + @JsonProperty("value") String value; } diff --git a/src/integrationtest/java/labapi/UserType.java b/src/integrationtest/java/labapi/UserType.java index 0ff84d9f..45000479 100644 --- a/src/integrationtest/java/labapi/UserType.java +++ b/src/integrationtest/java/labapi/UserType.java @@ -3,8 +3,6 @@ package labapi; -import com.google.gson.annotations.SerializedName; - public class UserType { public static final String CLOUD = "cloud"; public static final String FEDERATED = "federated"; diff --git a/src/main/java/com/microsoft/aad/msal4j/AadInstanceDiscovery.java b/src/main/java/com/microsoft/aad/msal4j/AadInstanceDiscoveryProvider.java similarity index 55% rename from src/main/java/com/microsoft/aad/msal4j/AadInstanceDiscovery.java rename to src/main/java/com/microsoft/aad/msal4j/AadInstanceDiscoveryProvider.java index e6a8ff6e..84bab38d 100644 --- a/src/main/java/com/microsoft/aad/msal4j/AadInstanceDiscovery.java +++ b/src/main/java/com/microsoft/aad/msal4j/AadInstanceDiscoveryProvider.java @@ -3,9 +3,6 @@ package com.microsoft.aad.msal4j; -import org.slf4j.Logger; -import org.slf4j.LoggerFactory; - import java.net.URL; import java.util.Arrays; import java.util.Collections; @@ -13,11 +10,17 @@ import java.util.TreeSet; import java.util.concurrent.ConcurrentHashMap; -class AadInstanceDiscovery { - private static final Logger log = LoggerFactory.getLogger(AadInstanceDiscovery.class); +class AadInstanceDiscoveryProvider { + + private final static String DEFAULT_TRUSTED_HOST = "login.microsoftonline.com"; + private final static String AUTHORIZE_ENDPOINT_TEMPLATE = "https://{host}/{tenant}/oauth2/v2.0/authorize"; + private final static String INSTANCE_DISCOVERY_ENDPOINT_TEMPLATE = "https://{host}/common/discovery/instance"; + private final static String INSTANCE_DISCOVERY_REQUEST_PARAMETERS_TEMPLATE = "?api-version=1.1&authorization_endpoint={authorizeEndpoint}"; final static TreeSet TRUSTED_HOSTS_SET = new TreeSet<>(String.CASE_INSENSITIVE_ORDER); + static ConcurrentHashMap cache = new ConcurrentHashMap<>(); + static { TRUSTED_HOSTS_SET.addAll(Arrays.asList( "login.windows.net", @@ -28,14 +31,19 @@ class AadInstanceDiscovery { "login.microsoftonline.us")); } - private final static String DEFAULT_TRUSTED_HOST = "login.microsoftonline.com"; + static InstanceDiscoveryMetadataEntry getMetadataEntry(URL authorityUrl, + boolean validateAuthority, + MsalRequest msalRequest, + ServiceBundle serviceBundle) { - private final static String AUTHORIZE_ENDPOINT_TEMPLATE = "https://{host}/{tenant}/oauth2/v2.0/authorize"; - private final static String INSTANCE_DISCOVERY_ENDPOINT_TEMPLATE = "https://{host}/common/discovery/instance"; - private final static String INSTANCE_DISCOVERY_REQUEST_PARAMETERS_TEMPLATE = - "?api-version=1.1&authorization_endpoint={authorizeEndpoint}"; + InstanceDiscoveryMetadataEntry result = cache.get(authorityUrl.getAuthority()); - static ConcurrentHashMap cache = new ConcurrentHashMap<>(); + if (result == null) { + doInstanceDiscoveryAndCache(authorityUrl, validateAuthority, msalRequest, serviceBundle); + } + + return cache.get(authorityUrl.getAuthority()); + } static Set getAliases(String host){ if(cache.containsKey(host)){ @@ -46,6 +54,38 @@ static Set getAliases(String host){ } } + static AadInstanceDiscoveryResponse parseInstanceDiscoveryMetadata(String instanceDiscoveryJson) { + + AadInstanceDiscoveryResponse aadInstanceDiscoveryResponse; + try { + aadInstanceDiscoveryResponse = JsonHelper.convertJsonToObject( + instanceDiscoveryJson, + AadInstanceDiscoveryResponse.class); + + } catch(Exception ex){ + throw new MsalClientException("Error parsing instance discovery response. Data must be " + + "in valid JSON format. For more information, see https://aka.ms/msal4j-instance-discovery", + AuthenticationErrorCode.INVALID_INSTANCE_DISCOVERY_METADATA); + } + + return aadInstanceDiscoveryResponse; + } + + static void cacheInstanceDiscoveryMetadata(String host, + AadInstanceDiscoveryResponse aadInstanceDiscoveryResponse) { + + if (aadInstanceDiscoveryResponse.metadata() != null) { + for (InstanceDiscoveryMetadataEntry entry : aadInstanceDiscoveryResponse.metadata()) { + for (String alias: entry.aliases()) { + cache.put(alias, entry); + } + } + } + cache.putIfAbsent(host, InstanceDiscoveryMetadataEntry.builder(). + preferredCache(host). + preferredNetwork(host).build()); + } + private static String getAuthorizeEndpoint(String host, String tenant) { return AUTHORIZE_ENDPOINT_TEMPLATE. replace("{host}", host). @@ -60,9 +100,9 @@ private static String getInstanceDiscoveryEndpoint(String host) { replace("{host}", discoveryHost); } - private static InstanceDiscoveryResponse sendInstanceDiscoveryRequest - (URL authorityUrl, MsalRequest msalRequest, - ServiceBundle serviceBundle) { + private static AadInstanceDiscoveryResponse sendInstanceDiscoveryRequest(URL authorityUrl, + MsalRequest msalRequest, + ServiceBundle serviceBundle) { String instanceDiscoveryRequestUrl = getInstanceDiscoveryEndpoint(authorityUrl.getAuthority()) + INSTANCE_DISCOVERY_REQUEST_PARAMETERS_TEMPLATE.replace("{authorizeEndpoint}", @@ -74,52 +114,32 @@ private static String getInstanceDiscoveryEndpoint(String host) { instanceDiscoveryRequestUrl, msalRequest.headers().getReadonlyHeaderMap()); - IHttpResponse httpResponse= HttpHelper.executeHttpRequest(httpRequest, msalRequest.requestContext(), serviceBundle); + IHttpResponse httpResponse= HttpHelper.executeHttpRequest( + httpRequest, + msalRequest.requestContext(), + serviceBundle); - return JsonHelper.convertJsonToObject(httpResponse.body(), InstanceDiscoveryResponse.class); + return JsonHelper.convertJsonToObject(httpResponse.body(), AadInstanceDiscoveryResponse.class); } - private static void validate(InstanceDiscoveryResponse instanceDiscoveryResponse) { - if (StringHelper.isBlank(instanceDiscoveryResponse.tenantDiscoveryEndpoint())) { - throw new MsalServiceException(instanceDiscoveryResponse); - } - } - - private static void doInstanceDiscoveryAndCache - (URL authorityUrl, boolean validateAuthority, MsalRequest msalRequest, ServiceBundle serviceBundle) throws Exception { + private static void doInstanceDiscoveryAndCache(URL authorityUrl, + boolean validateAuthority, + MsalRequest msalRequest, + ServiceBundle serviceBundle) { - InstanceDiscoveryResponse instanceDiscoveryResponse = + AadInstanceDiscoveryResponse aadInstanceDiscoveryResponse = sendInstanceDiscoveryRequest(authorityUrl, msalRequest, serviceBundle); if (validateAuthority) { - validate(instanceDiscoveryResponse); - } - - cacheInstanceDiscoveryMetadata(authorityUrl.getAuthority(), instanceDiscoveryResponse); - } - - static InstanceDiscoveryMetadataEntry GetMetadataEntry - (URL authorityUrl, boolean validateAuthority, MsalRequest msalRequest, ServiceBundle serviceBundle) throws Exception { - - InstanceDiscoveryMetadataEntry result = cache.get(authorityUrl.getAuthority()); - - if (result == null) { - doInstanceDiscoveryAndCache(authorityUrl, validateAuthority, msalRequest, serviceBundle); + validate(aadInstanceDiscoveryResponse); } - return cache.get(authorityUrl.getAuthority()); + cacheInstanceDiscoveryMetadata(authorityUrl.getAuthority(), aadInstanceDiscoveryResponse); } - private static void cacheInstanceDiscoveryMetadata(String host, InstanceDiscoveryResponse instanceDiscoveryResponse) { - if (instanceDiscoveryResponse.metadata() != null) { - for (InstanceDiscoveryMetadataEntry entry : instanceDiscoveryResponse.metadata()) { - for (String alias : entry.aliases) { - cache.put(alias, entry); - } - } + private static void validate(AadInstanceDiscoveryResponse aadInstanceDiscoveryResponse) { + if (StringHelper.isBlank(aadInstanceDiscoveryResponse.tenantDiscoveryEndpoint())) { + throw new MsalServiceException(aadInstanceDiscoveryResponse); } - cache.putIfAbsent(host, InstanceDiscoveryMetadataEntry.builder(). - preferredCache(host). - preferredNetwork(host).build()); } } diff --git a/src/main/java/com/microsoft/aad/msal4j/InstanceDiscoveryResponse.java b/src/main/java/com/microsoft/aad/msal4j/AadInstanceDiscoveryResponse.java similarity index 62% rename from src/main/java/com/microsoft/aad/msal4j/InstanceDiscoveryResponse.java rename to src/main/java/com/microsoft/aad/msal4j/AadInstanceDiscoveryResponse.java index c98eba4f..c940c24d 100644 --- a/src/main/java/com/microsoft/aad/msal4j/InstanceDiscoveryResponse.java +++ b/src/main/java/com/microsoft/aad/msal4j/AadInstanceDiscoveryResponse.java @@ -3,30 +3,30 @@ package com.microsoft.aad.msal4j; -import com.google.gson.annotations.SerializedName; +import com.fasterxml.jackson.annotation.JsonProperty; import lombok.AccessLevel; import lombok.Getter; import lombok.experimental.Accessors; @Accessors(fluent = true) @Getter(AccessLevel.PACKAGE) -class InstanceDiscoveryResponse { +class AadInstanceDiscoveryResponse { - @SerializedName("tenant_discovery_endpoint") + @JsonProperty("tenant_discovery_endpoint") private String tenantDiscoveryEndpoint; - @SerializedName("metadata") + @JsonProperty("metadata") private InstanceDiscoveryMetadataEntry[] metadata; - @SerializedName("error_description") + @JsonProperty("error_description") private String errorDescription; - @SerializedName("error_codes") + @JsonProperty("error_codes") private long[] errorCodes; - @SerializedName("error") + @JsonProperty("error") private String error; - @SerializedName("correlation_id") + @JsonProperty("correlation_id") private String correlationId; } diff --git a/src/main/java/com/microsoft/aad/msal4j/AccessTokenCacheEntity.java b/src/main/java/com/microsoft/aad/msal4j/AccessTokenCacheEntity.java index 17062226..9ce03de4 100644 --- a/src/main/java/com/microsoft/aad/msal4j/AccessTokenCacheEntity.java +++ b/src/main/java/com/microsoft/aad/msal4j/AccessTokenCacheEntity.java @@ -3,7 +3,7 @@ package com.microsoft.aad.msal4j; -import com.google.gson.annotations.SerializedName; +import com.fasterxml.jackson.annotation.JsonProperty; import lombok.Getter; import lombok.Setter; import lombok.experimental.Accessors; @@ -16,22 +16,22 @@ @Setter class AccessTokenCacheEntity extends Credential { - @SerializedName("credential_type") + @JsonProperty("credential_type") private String credentialType; - @SerializedName("realm") + @JsonProperty("realm") protected String realm; - @SerializedName("target") + @JsonProperty("target") private String target; - @SerializedName("cached_at") + @JsonProperty("cached_at") private String cachedAt; - @SerializedName("expires_on") + @JsonProperty("expires_on") private String expiresOn; - @SerializedName("extended_expires_on") + @JsonProperty("extended_expires_on") private String extExpiresOn; String getKey() { diff --git a/src/main/java/com/microsoft/aad/msal4j/AccountCacheEntity.java b/src/main/java/com/microsoft/aad/msal4j/AccountCacheEntity.java index c22b4e47..23ba4113 100644 --- a/src/main/java/com/microsoft/aad/msal4j/AccountCacheEntity.java +++ b/src/main/java/com/microsoft/aad/msal4j/AccountCacheEntity.java @@ -3,7 +3,7 @@ package com.microsoft.aad.msal4j; -import com.google.gson.annotations.SerializedName; +import com.fasterxml.jackson.annotation.JsonProperty; import lombok.*; import lombok.experimental.Accessors; @@ -20,33 +20,33 @@ class AccountCacheEntity implements Serializable { static final String MSSTS_ACCOUNT_TYPE = "MSSTS"; static final String ADFS_ACCOUNT_TYPE = "ADFS"; - @SerializedName("home_account_id") + @JsonProperty("home_account_id") protected String homeAccountId; - @SerializedName("environment") + @JsonProperty("environment") protected String environment; @EqualsAndHashCode.Exclude - @SerializedName("realm") + @JsonProperty("realm") protected String realm; - @SerializedName("local_account_id") + @JsonProperty("local_account_id") protected String localAccountId; - @SerializedName("username") + @JsonProperty("username") protected String username; - @SerializedName("name") + @JsonProperty("name") protected String name; - @SerializedName("client_info") + @JsonProperty("client_info") protected String clientInfoStr; ClientInfo clientInfo() { return ClientInfo.createFromJson(clientInfoStr); } - @SerializedName("authority_type") + @JsonProperty("authority_type") protected String authorityType; String getKey() { diff --git a/src/main/java/com/microsoft/aad/msal4j/AccountsSupplier.java b/src/main/java/com/microsoft/aad/msal4j/AccountsSupplier.java index dcc58f4a..92eaf642 100644 --- a/src/main/java/com/microsoft/aad/msal4j/AccountsSupplier.java +++ b/src/main/java/com/microsoft/aad/msal4j/AccountsSupplier.java @@ -23,11 +23,11 @@ class AccountsSupplier implements Supplier> { public Set get() { try { InstanceDiscoveryMetadataEntry instanceDiscoveryData = - AadInstanceDiscovery.GetMetadataEntry - (new URL(clientApplication.authority()), - clientApplication.validateAuthority(), - msalRequest, - clientApplication.getServiceBundle()); + AadInstanceDiscoveryProvider.getMetadataEntry( + new URL(clientApplication.authority()), + clientApplication.validateAuthority(), + msalRequest, + clientApplication.getServiceBundle()); return clientApplication.tokenCache.getAccounts (clientApplication.clientId(), instanceDiscoveryData.aliases); diff --git a/src/main/java/com/microsoft/aad/msal4j/AcquireTokenByAuthorizationGrantSupplier.java b/src/main/java/com/microsoft/aad/msal4j/AcquireTokenByAuthorizationGrantSupplier.java index a967adbf..f9ad2980 100644 --- a/src/main/java/com/microsoft/aad/msal4j/AcquireTokenByAuthorizationGrantSupplier.java +++ b/src/main/java/com/microsoft/aad/msal4j/AcquireTokenByAuthorizationGrantSupplier.java @@ -7,11 +7,11 @@ import com.nimbusds.oauth2.sdk.AuthorizationGrant; import com.nimbusds.oauth2.sdk.ResourceOwnerPasswordCredentialsGrant; import com.nimbusds.oauth2.sdk.SAML2BearerGrant; -import org.apache.commons.codec.binary.Base64; import java.io.UnsupportedEncodingException; import java.net.URLEncoder; import java.nio.charset.StandardCharsets; +import java.util.Base64; class AcquireTokenByAuthorizationGrantSupplier extends AuthenticationResultSupplier { @@ -93,10 +93,10 @@ private AuthorizationGrant getSAMLAuthorizationGrant(WSTrustResponse response) t AuthorizationGrant updatedGrant; if (response.isTokenSaml2()) { updatedGrant = new SAML2BearerGrant(new Base64URL( - Base64.encodeBase64String(response.getToken().getBytes(StandardCharsets.UTF_8)))); + Base64.getEncoder().encodeToString(response.getToken().getBytes(StandardCharsets.UTF_8)))); } else { updatedGrant = new SAML11BearerGrant(new Base64URL( - Base64.encodeBase64String(response.getToken() + Base64.getEncoder().encodeToString(response.getToken() .getBytes(StandardCharsets.UTF_8)))); } return updatedGrant; diff --git a/src/main/java/com/microsoft/aad/msal4j/AppMetadataCacheEntity.java b/src/main/java/com/microsoft/aad/msal4j/AppMetadataCacheEntity.java index 6cb2fc5b..3c1eeb45 100644 --- a/src/main/java/com/microsoft/aad/msal4j/AppMetadataCacheEntity.java +++ b/src/main/java/com/microsoft/aad/msal4j/AppMetadataCacheEntity.java @@ -3,7 +3,7 @@ package com.microsoft.aad.msal4j; -import com.google.gson.annotations.SerializedName; +import com.fasterxml.jackson.annotation.JsonProperty; import lombok.Getter; import lombok.Setter; import lombok.experimental.Accessors; @@ -18,13 +18,13 @@ class AppMetadataCacheEntity { public static final String APP_METADATA_CACHE_ENTITY_ID = "appmetadata"; - @SerializedName("client_id") + @JsonProperty("client_id") private String clientId; - @SerializedName("environment") + @JsonProperty("environment") private String environment; - @SerializedName("family_id") + @JsonProperty("family_id") private String familyId; String getKey(){ diff --git a/src/main/java/com/microsoft/aad/msal4j/AuthenticationErrorCode.java b/src/main/java/com/microsoft/aad/msal4j/AuthenticationErrorCode.java index d1638a81..d4cb0158 100644 --- a/src/main/java/com/microsoft/aad/msal4j/AuthenticationErrorCode.java +++ b/src/main/java/com/microsoft/aad/msal4j/AuthenticationErrorCode.java @@ -46,6 +46,12 @@ public class AuthenticationErrorCode { */ public final static String CACHE_MISS = "cache_miss"; + /** + * Not able to parse instance discovery metadata. Ensure data is in valid JSON format, and that + * it contains relevant fields. For more information, see https://aka.ms/msal4j-instance-discovery + */ + public final static String INVALID_INSTANCE_DISCOVERY_METADATA = "invalid_instance_discovery_metadata"; + /** * Unknown error occurred */ diff --git a/src/main/java/com/microsoft/aad/msal4j/AuthenticationResult.java b/src/main/java/com/microsoft/aad/msal4j/AuthenticationResult.java index 30381276..44885c2e 100644 --- a/src/main/java/com/microsoft/aad/msal4j/AuthenticationResult.java +++ b/src/main/java/com/microsoft/aad/msal4j/AuthenticationResult.java @@ -45,8 +45,8 @@ private IdToken getIdTokenObj() { } try { String idTokenJson = JWTParser.parse(idToken).getParsedParts()[1].decodeToString(); - return JsonHelper.convertJsonToObject(idTokenJson, IdToken.class); + return JsonHelper.convertJsonToObject(idTokenJson, IdToken.class); } catch (ParseException e) { e.printStackTrace(); } diff --git a/src/main/java/com/microsoft/aad/msal4j/AuthenticationResultSupplier.java b/src/main/java/com/microsoft/aad/msal4j/AuthenticationResultSupplier.java index 389dff24..60c1887e 100644 --- a/src/main/java/com/microsoft/aad/msal4j/AuthenticationResultSupplier.java +++ b/src/main/java/com/microsoft/aad/msal4j/AuthenticationResultSupplier.java @@ -3,7 +3,7 @@ package com.microsoft.aad.msal4j; -import org.apache.commons.codec.binary.Base64; +import java.util.Base64; import java.io.UnsupportedEncodingException; import java.net.URI; @@ -29,9 +29,11 @@ Authority getAuthorityWithPrefNetworkHost(String authority) throws Exception { URL authorityUrl = new URL(authority); InstanceDiscoveryMetadataEntry discoveryMetadataEntry = - AadInstanceDiscovery.GetMetadataEntry - (authorityUrl, clientApplication.validateAuthority(), msalRequest, - clientApplication.getServiceBundle()); + AadInstanceDiscoveryProvider.getMetadataEntry( + authorityUrl, + clientApplication.validateAuthority(), + msalRequest, + clientApplication.getServiceBundle()); URL updatedAuthorityUrl = new URL(authorityUrl.getProtocol(), discoveryMetadataEntry.preferredNetwork, authorityUrl.getFile()); @@ -154,7 +156,7 @@ private String computeSha256Hash(String input) { MessageDigest digest = MessageDigest.getInstance("SHA-256"); digest.update(input.getBytes("UTF-8")); byte[] hash = digest.digest(); - return Base64.encodeBase64URLSafeString(hash); + return Base64.getUrlEncoder().encodeToString(hash); } catch (NoSuchAlgorithmException | UnsupportedEncodingException ex){ clientApplication.log.warn(LogHelper.createMessage( diff --git a/src/main/java/com/microsoft/aad/msal4j/ClientApplicationBase.java b/src/main/java/com/microsoft/aad/msal4j/ClientApplicationBase.java index b01202b8..4b344e54 100644 --- a/src/main/java/com/microsoft/aad/msal4j/ClientApplicationBase.java +++ b/src/main/java/com/microsoft/aad/msal4j/ClientApplicationBase.java @@ -28,6 +28,8 @@ abstract class ClientApplicationBase implements IClientApplicationBase { protected Logger log; protected ClientAuthentication clientAuthentication; + protected Authority authenticationAuthority; + private ServiceBundle serviceBundle; @Accessors(fluent = true) @Getter @@ -37,8 +39,6 @@ abstract class ClientApplicationBase implements IClientApplicationBase { @Getter private String authority; - protected Authority authenticationAuthority; - @Accessors(fluent = true) @Getter private boolean validateAuthority; @@ -51,8 +51,6 @@ abstract class ClientApplicationBase implements IClientApplicationBase { @Getter private boolean logPii; - private ServiceBundle serviceBundle; - @Accessors(fluent = true) @Getter(AccessLevel.PACKAGE) private Consumer>> telemetryConsumer; @@ -77,6 +75,10 @@ abstract class ClientApplicationBase implements IClientApplicationBase { @Getter private String applicationVersion; + @Accessors(fluent = true) + @Getter + private AadInstanceDiscoveryResponse aadAadInstanceDiscoveryResponse; + @Override public CompletableFuture acquireToken(AuthorizationCodeParameters parameters) { @@ -177,7 +179,7 @@ AuthenticationResult acquireTokenCommon(MsalRequest msalRequest, Authority reque if(authenticationAuthority.authorityType.equals(AuthorityType.AAD)){ InstanceDiscoveryMetadataEntry instanceDiscoveryMetadata = - AadInstanceDiscovery.GetMetadataEntry( + AadInstanceDiscoveryProvider.getMetadataEntry( requestAuthority.canonicalAuthorityUrl(), validateAuthority, msalRequest, @@ -244,6 +246,7 @@ abstract static class Builder> { private String applicationName; private String applicationVersion; private ITokenCacheAccessAspect tokenCacheAccessAspect; + private AadInstanceDiscoveryResponse aadInstanceDiscoveryResponse; /** * Constructor to create instance of Builder of client application @@ -298,7 +301,10 @@ public T b2cAuthority(String val) throws MalformedURLException{ /** * Set a boolean value telling the application if the authority needs to be verified - * against a list of known authorities. + * against a list of known authorities. Authority is only validated when: + * 1 - It is an Azure Active Directory authority (not B2C or ADFS) + * 2 - Instance discovery metadata is not set via {@link ClientApplicationBase#aadAadInstanceDiscoveryResponse} + * * The default value is true. * * @param val a boolean value for validateAuthority @@ -449,6 +455,27 @@ public T setTokenCacheAccessAspect(ITokenCacheAccessAspect val) { return self(); } + /** + * Sets instance discovery response data which will be used for determining tenant discovery + * endpoint and authority aliases. + * + * Note that authority validation is not done even if {@link ClientApplicationBase#validateAuthority} + * is set to true. + * + * For more information, see + * https://aka.ms/msal4j-instance-discovery + * @param val JSON formatted value of response from AAD instance discovery endpoint + * @return instance of the Builder on which method was called + */ + public T aadInstanceDiscoveryResponse(String val) { + validateNotNull("aadInstanceDiscoveryResponse", val); + + aadInstanceDiscoveryResponse = + AadInstanceDiscoveryProvider.parseInstanceDiscoveryMetadata(val); + + return self(); + } + private static Authority createDefaultAADAuthority() { Authority authority; try { @@ -481,5 +508,12 @@ private static Authority createDefaultAADAuthority() { new TelemetryManager(telemetryConsumer, builder.onlySendFailureTelemetry)); authenticationAuthority = builder.authenticationAuthority; tokenCache = new TokenCache(builder.tokenCacheAccessAspect); + aadAadInstanceDiscoveryResponse = builder.aadInstanceDiscoveryResponse; + + if(aadAadInstanceDiscoveryResponse != null){ + AadInstanceDiscoveryProvider.cacheInstanceDiscoveryMetadata( + authenticationAuthority.host, + aadAadInstanceDiscoveryResponse); + } } } \ No newline at end of file diff --git a/src/main/java/com/microsoft/aad/msal4j/ClientCertificate.java b/src/main/java/com/microsoft/aad/msal4j/ClientCertificate.java index c5e3a40f..37d93722 100644 --- a/src/main/java/com/microsoft/aad/msal4j/ClientCertificate.java +++ b/src/main/java/com/microsoft/aad/msal4j/ClientCertificate.java @@ -22,7 +22,7 @@ import lombok.Getter; import lombok.experimental.Accessors; -import org.apache.commons.codec.binary.Base64; +import java.util.Base64; final class ClientCertificate implements IClientCertificate { @@ -72,12 +72,12 @@ else if("sun.security.mscapi.RSAPrivateKey".equals(key.getClass().getName()) || public String publicCertificateHash() throws CertificateEncodingException, NoSuchAlgorithmException { - return Base64.encodeBase64String(ClientCertificate + return Base64.getEncoder().encodeToString(ClientCertificate .getHash(this.publicCertificate.getEncoded())); } public String publicCertificate() throws CertificateEncodingException { - return Base64.encodeBase64String(this.publicCertificate.getEncoded()); + return Base64.getEncoder().encodeToString(this.publicCertificate.getEncoded()); } static ClientCertificate create(final InputStream pkcs12Certificate, final String password) diff --git a/src/main/java/com/microsoft/aad/msal4j/ClientInfo.java b/src/main/java/com/microsoft/aad/msal4j/ClientInfo.java index 098b54bd..2f7ee222 100644 --- a/src/main/java/com/microsoft/aad/msal4j/ClientInfo.java +++ b/src/main/java/com/microsoft/aad/msal4j/ClientInfo.java @@ -3,7 +3,7 @@ package com.microsoft.aad.msal4j; -import com.google.gson.annotations.SerializedName; +import com.fasterxml.jackson.annotation.JsonProperty; import com.nimbusds.jose.util.StandardCharset; import lombok.AccessLevel; import lombok.Getter; @@ -13,10 +13,10 @@ @Getter(AccessLevel.PACKAGE) class ClientInfo { - @SerializedName("uid") + @JsonProperty("uid") private String uniqueIdentifier; - @SerializedName("utid") + @JsonProperty("utid") private String unqiueTenantIdentifier; public static ClientInfo createFromJson(String clientInfoJsonBase64Encoded){ diff --git a/src/main/java/com/microsoft/aad/msal4j/Credential.java b/src/main/java/com/microsoft/aad/msal4j/Credential.java index ca2370f9..b73c7d67 100644 --- a/src/main/java/com/microsoft/aad/msal4j/Credential.java +++ b/src/main/java/com/microsoft/aad/msal4j/Credential.java @@ -3,7 +3,7 @@ package com.microsoft.aad.msal4j; -import com.google.gson.annotations.SerializedName; +import com.fasterxml.jackson.annotation.JsonProperty; import lombok.Getter; import lombok.Setter; import lombok.experimental.Accessors; @@ -13,15 +13,15 @@ @Setter class Credential { - @SerializedName("home_account_id") + @JsonProperty("home_account_id") protected String homeAccountId; - @SerializedName("environment") + @JsonProperty("environment") protected String environment; - @SerializedName("client_id") + @JsonProperty("client_id") protected String clientId; - @SerializedName("secret") + @JsonProperty("secret") protected String secret; } diff --git a/src/main/java/com/microsoft/aad/msal4j/DeviceCode.java b/src/main/java/com/microsoft/aad/msal4j/DeviceCode.java index 548f8702..8af17104 100644 --- a/src/main/java/com/microsoft/aad/msal4j/DeviceCode.java +++ b/src/main/java/com/microsoft/aad/msal4j/DeviceCode.java @@ -3,7 +3,7 @@ package com.microsoft.aad.msal4j; -import com.google.gson.annotations.SerializedName; +import com.fasterxml.jackson.annotation.JsonProperty; import lombok.AccessLevel; import lombok.Getter; import lombok.Setter; @@ -20,37 +20,37 @@ public final class DeviceCode { /** * code which user needs to provide when authenticating at the verification URI */ - @SerializedName("user_code") + @JsonProperty("user_code") private String userCode; /** * code which should be included in the request for the access token */ - @SerializedName("device_code") + @JsonProperty("device_code") private String deviceCode; /** * URI where user can authenticate */ - @SerializedName("verification_uri") + @JsonProperty("verification_uri") private String verificationUri; /** * expiration time of device code in seconds. */ - @SerializedName("expires_in") + @JsonProperty("expires_in") private long expiresIn; /** * interval at which the STS should be polled at */ - @SerializedName("interval") + @JsonProperty("interval") private long interval; /** * message which should be displayed to the user. */ - @SerializedName("message") + @JsonProperty("message") private String message; @Getter(AccessLevel.PROTECTED) diff --git a/src/main/java/com/microsoft/aad/msal4j/ErrorResponse.java b/src/main/java/com/microsoft/aad/msal4j/ErrorResponse.java index 7ecede74..064215e7 100644 --- a/src/main/java/com/microsoft/aad/msal4j/ErrorResponse.java +++ b/src/main/java/com/microsoft/aad/msal4j/ErrorResponse.java @@ -3,7 +3,7 @@ package com.microsoft.aad.msal4j; -import com.google.gson.annotations.SerializedName; +import com.fasterxml.jackson.annotation.JsonProperty; import lombok.Getter; import lombok.Setter; import lombok.experimental.Accessors; @@ -16,27 +16,27 @@ class ErrorResponse { private Integer statusCode; private String statusMessage; - @SerializedName("error") + @JsonProperty("error") protected String error; - @SerializedName("error_description") + @JsonProperty("error_description") protected String errorDescription; - @SerializedName("error_codes") + @JsonProperty("error_codes") protected long[] errorCodes; - @SerializedName("suberror") + @JsonProperty("suberror") protected String subError; - @SerializedName("trace_id") + @JsonProperty("trace_id") protected String traceId; - @SerializedName("timestamp") + @JsonProperty("timestamp") protected String timestamp; - @SerializedName("correlation_id") + @JsonProperty("correlation_id") protected String correlation_id; - @SerializedName("claims") + @JsonProperty("claims") private String claims; } diff --git a/src/main/java/com/microsoft/aad/msal4j/Event.java b/src/main/java/com/microsoft/aad/msal4j/Event.java index 6baf2598..330ae60f 100644 --- a/src/main/java/com/microsoft/aad/msal4j/Event.java +++ b/src/main/java/com/microsoft/aad/msal4j/Event.java @@ -8,7 +8,6 @@ import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.time.Instant; -import java.util.Arrays; import java.util.Base64; import java.util.HashMap; import java.util.Map; @@ -45,7 +44,7 @@ static String scrubTenant(URI uri){ if(!uri.isAbsolute()){ throw new IllegalArgumentException("Requires an absolute URI"); } - if(!AadInstanceDiscovery.TRUSTED_HOSTS_SET.contains(uri.getHost())){ + if(!AadInstanceDiscoveryProvider.TRUSTED_HOSTS_SET.contains(uri.getHost())){ return null; } diff --git a/src/main/java/com/microsoft/aad/msal4j/IdToken.java b/src/main/java/com/microsoft/aad/msal4j/IdToken.java index ead4e635..b5ee6a0a 100644 --- a/src/main/java/com/microsoft/aad/msal4j/IdToken.java +++ b/src/main/java/com/microsoft/aad/msal4j/IdToken.java @@ -3,7 +3,7 @@ package com.microsoft.aad.msal4j; -import com.google.gson.annotations.SerializedName; +import com.fasterxml.jackson.annotation.JsonProperty; import com.nimbusds.jwt.JWTClaimsSet; import java.text.ParseException; @@ -23,40 +23,40 @@ class IdToken { static final String UPN = "upn"; static final String UNIQUE_NAME = "unique_name"; - @SerializedName("iss") + @JsonProperty("iss") protected String issuer; - @SerializedName("sub") + @JsonProperty("sub") protected String subject; - @SerializedName("aud") + @JsonProperty("aud") protected String audience ; - @SerializedName("exp") + @JsonProperty("exp") protected Long expirationTime; - @SerializedName("iat") + @JsonProperty("iat") protected Long issuedAt; - @SerializedName("nbf") + @JsonProperty("nbf") protected Long notBefore; - @SerializedName("name") + @JsonProperty("name") protected String name; - @SerializedName("preferred_username") + @JsonProperty("preferred_username") protected String preferredUsername; - @SerializedName("oid") + @JsonProperty("oid") protected String objectIdentifier; - @SerializedName("tid") + @JsonProperty("tid") protected String tenantIdentifier; - @SerializedName("upn") + @JsonProperty("upn") protected String upn; - @SerializedName("unique_name") + @JsonProperty("unique_name") protected String uniqueName; static IdToken createFromJWTClaims(final JWTClaimsSet claims) throws ParseException { diff --git a/src/main/java/com/microsoft/aad/msal4j/IdTokenCacheEntity.java b/src/main/java/com/microsoft/aad/msal4j/IdTokenCacheEntity.java index 5aae730e..21052f8a 100644 --- a/src/main/java/com/microsoft/aad/msal4j/IdTokenCacheEntity.java +++ b/src/main/java/com/microsoft/aad/msal4j/IdTokenCacheEntity.java @@ -3,9 +3,7 @@ package com.microsoft.aad.msal4j; -import com.google.gson.annotations.SerializedName; -import com.microsoft.aad.msal4j.Constants; -import com.microsoft.aad.msal4j.Credential; +import com.fasterxml.jackson.annotation.JsonProperty; import lombok.Getter; import lombok.Setter; import lombok.experimental.Accessors; @@ -18,10 +16,10 @@ @Setter class IdTokenCacheEntity extends Credential { - @SerializedName("credential_type") + @JsonProperty("credential_type") private String credentialType; - @SerializedName("realm") + @JsonProperty("realm") protected String realm; String getKey(){ diff --git a/src/main/java/com/microsoft/aad/msal4j/InstanceDiscoveryMetadataEntry.java b/src/main/java/com/microsoft/aad/msal4j/InstanceDiscoveryMetadataEntry.java index e0f12cea..a8a52a57 100644 --- a/src/main/java/com/microsoft/aad/msal4j/InstanceDiscoveryMetadataEntry.java +++ b/src/main/java/com/microsoft/aad/msal4j/InstanceDiscoveryMetadataEntry.java @@ -3,7 +3,7 @@ package com.microsoft.aad.msal4j; -import com.google.gson.annotations.SerializedName; +import com.fasterxml.jackson.annotation.JsonProperty; import lombok.AccessLevel; import lombok.Builder; import lombok.Getter; @@ -16,25 +16,12 @@ @Builder class InstanceDiscoveryMetadataEntry { - @SerializedName("preferred_network") + @JsonProperty("preferred_network") String preferredNetwork; - @SerializedName("preferred_cache") + @JsonProperty("preferred_cache") String preferredCache; - @SerializedName("aliases") + @JsonProperty("aliases") Set aliases; - - public static class InstanceDiscoveryMetadataEntryBuilder{ - public InstanceDiscoveryMetadataEntryBuilder aliases(String[] aliasesArray) { - Set set = new TreeSet<>(String.CASE_INSENSITIVE_ORDER); - - if(aliasesArray != null){ - set.addAll(Arrays.asList(aliasesArray)); - } - aliases = Collections.unmodifiableSet(set); - - return this; - } - } } diff --git a/src/main/java/com/microsoft/aad/msal4j/JsonHelper.java b/src/main/java/com/microsoft/aad/msal4j/JsonHelper.java index 53634c9c..d453d285 100644 --- a/src/main/java/com/microsoft/aad/msal4j/JsonHelper.java +++ b/src/main/java/com/microsoft/aad/msal4j/JsonHelper.java @@ -3,25 +3,30 @@ package com.microsoft.aad.msal4j; -import java.io.Reader; -import java.io.StringReader; +import com.fasterxml.jackson.annotation.JsonInclude; +import com.fasterxml.jackson.core.JsonParser; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.core.json.JsonReadFeature; +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.ObjectMapper; +import com.fasterxml.jackson.databind.SerializationFeature; +import com.fasterxml.jackson.databind.json.JsonMapper; -import com.google.gson.Gson; -import com.google.gson.GsonBuilder; - -/** - * - */ class JsonHelper { - /** - * - * @param json - * @param clazz - * @return - */ + static ObjectMapper mapper; + + static { + mapper = new ObjectMapper(); + mapper.configure(DeserializationFeature.FAIL_ON_UNKNOWN_PROPERTIES, false); + mapper.configure(JsonParser.Feature.ALLOW_UNQUOTED_CONTROL_CHARS, true); + mapper.setSerializationInclusion(JsonInclude.Include.NON_NULL); + } + static T convertJsonToObject(final String json, final Class clazz) { - final Reader reader = new StringReader(json); - final Gson gson = new GsonBuilder().create(); - return gson.fromJson(reader, clazz); + try { + return mapper.readValue(json, clazz); + } catch (JsonProcessingException e) { + throw new MsalClientException(e); + } } } diff --git a/src/main/java/com/microsoft/aad/msal4j/MsalServiceException.java b/src/main/java/com/microsoft/aad/msal4j/MsalServiceException.java index 8c8aa3a4..61d7b36b 100644 --- a/src/main/java/com/microsoft/aad/msal4j/MsalServiceException.java +++ b/src/main/java/com/microsoft/aad/msal4j/MsalServiceException.java @@ -79,7 +79,7 @@ public MsalServiceException( * Initializes a new instance of the exception class * @param discoveryResponse response object from instance discovery network call */ - public MsalServiceException(final InstanceDiscoveryResponse discoveryResponse){ + public MsalServiceException(final AadInstanceDiscoveryResponse discoveryResponse){ super(discoveryResponse.errorDescription(), discoveryResponse.error()); this.correlationId = discoveryResponse.correlationId(); diff --git a/src/main/java/com/microsoft/aad/msal4j/RefreshTokenCacheEntity.java b/src/main/java/com/microsoft/aad/msal4j/RefreshTokenCacheEntity.java index ce619b00..cd9be21e 100644 --- a/src/main/java/com/microsoft/aad/msal4j/RefreshTokenCacheEntity.java +++ b/src/main/java/com/microsoft/aad/msal4j/RefreshTokenCacheEntity.java @@ -3,7 +3,7 @@ package com.microsoft.aad.msal4j; -import com.google.gson.annotations.SerializedName; +import com.fasterxml.jackson.annotation.JsonProperty; import lombok.Getter; import lombok.Setter; import lombok.experimental.Accessors; @@ -16,10 +16,10 @@ @Setter class RefreshTokenCacheEntity extends Credential { - @SerializedName("credential_type") + @JsonProperty("credential_type") private String credentialType; - @SerializedName("family_id") + @JsonProperty("family_id") private String family_id; boolean isFamilyRT(){ diff --git a/src/main/java/com/microsoft/aad/msal4j/RemoveAccountRunnable.java b/src/main/java/com/microsoft/aad/msal4j/RemoveAccountRunnable.java index 1651a117..6979b02e 100644 --- a/src/main/java/com/microsoft/aad/msal4j/RemoveAccountRunnable.java +++ b/src/main/java/com/microsoft/aad/msal4j/RemoveAccountRunnable.java @@ -21,7 +21,8 @@ class RemoveAccountRunnable implements Runnable { @Override public void run() { try { - Set aliases = AadInstanceDiscovery.getAliases(clientApplication.authenticationAuthority.host()); + Set aliases = AadInstanceDiscoveryProvider.getAliases( + clientApplication.authenticationAuthority.host()); clientApplication.tokenCache.removeAccount (clientApplication.clientId(), account, aliases); diff --git a/src/main/java/com/microsoft/aad/msal4j/TokenCache.java b/src/main/java/com/microsoft/aad/msal4j/TokenCache.java index 63f54078..4ee6e011 100644 --- a/src/main/java/com/microsoft/aad/msal4j/TokenCache.java +++ b/src/main/java/com/microsoft/aad/msal4j/TokenCache.java @@ -3,9 +3,10 @@ package com.microsoft.aad.msal4j; -import com.google.gson.*; -import com.google.gson.annotations.SerializedName; -import com.google.gson.internal.LinkedTreeMap; +import com.fasterxml.jackson.annotation.JsonProperty; +import com.fasterxml.jackson.core.JsonProcessingException; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.node.ObjectNode; import java.util.*; import java.util.concurrent.locks.ReadWriteLock; @@ -38,20 +39,20 @@ public TokenCache(ITokenCacheAccessAspect tokenCacheAccessAspect) { public TokenCache() { } - @SerializedName("AccessToken") - Map accessTokens = new LinkedTreeMap<>(); + @JsonProperty("AccessToken") + Map accessTokens = new LinkedHashMap<>(); - @SerializedName("RefreshToken") - Map refreshTokens = new LinkedTreeMap<>(); + @JsonProperty("RefreshToken") + Map refreshTokens = new LinkedHashMap<>(); - @SerializedName("IdToken") - Map idTokens = new LinkedTreeMap<>(); + @JsonProperty("IdToken") + Map idTokens = new LinkedHashMap<>(); - @SerializedName("Account") - Map accounts = new LinkedTreeMap<>(); + @JsonProperty("Account") + Map accounts = new LinkedHashMap<>(); - @SerializedName("AppMetadata") - Map appMetadata = new LinkedTreeMap<>(); + @JsonProperty("AppMetadata") + Map appMetadata = new LinkedHashMap<>(); transient ITokenCacheAccessAspect tokenCacheAccessAspect; @@ -63,9 +64,8 @@ public void deserialize(String data) { return; } serializedCachedSnapshot = data; - Gson gson = new GsonBuilder().create(); - TokenCache deserializedCache = gson.fromJson(data, TokenCache.class); + TokenCache deserializedCache = JsonHelper.convertJsonToObject(data, TokenCache.class); lock.writeLock().lock(); try { @@ -79,47 +79,48 @@ public void deserialize(String data) { } } - private static void mergeJsonObjects(JsonObject old, JsonObject update) { + private static void mergeJsonObjects(JsonNode old, JsonNode update) { mergeRemovals(old, update); mergeUpdates(old, update); } - private static void mergeUpdates(JsonObject old, JsonObject update) { - for (Map.Entry uEntry : update.entrySet()) { - String key = uEntry.getKey(); - JsonElement uValue = uEntry.getValue(); + private static void mergeUpdates(JsonNode old, JsonNode update) { + Iterator fieldNames = update.fieldNames(); + while (fieldNames.hasNext()) { + String uKey = fieldNames.next(); + JsonNode uValue = update.get(uKey); // add new property - if (!old.has(key)) { - if (!uValue.isJsonNull() && - !(uValue.isJsonObject() && uValue.getAsJsonObject().size() == 0)) { - old.add(key, uValue); + if (!old.has(uKey)) { + if (!uValue.isNull() && + !(uValue.isObject() && uValue.size() == 0)) { + ((ObjectNode)old).set(uKey, uValue); } } // merge old and new property else { - JsonElement oValue = old.get(key); - if (uValue.isJsonObject()) { - mergeUpdates(oValue.getAsJsonObject(), uValue.getAsJsonObject()); + JsonNode oValue = old.get(uKey); + if (uValue.isObject()) { + mergeUpdates(oValue, uValue); } else { - old.add(key, uValue); + ((ObjectNode)old).set(uKey, uValue); } } } } - private static void mergeRemovals(JsonObject old, JsonObject update) { + private static void mergeRemovals(JsonNode old, JsonNode update) { Set msalEntities = new HashSet<>(Arrays.asList("Account", "AccessToken", "RefreshToken", "IdToken", "AppMetadata")); for (String msalEntity : msalEntities) { - JsonObject oldEntries = old.getAsJsonObject(msalEntity); - JsonObject newEntries = update.getAsJsonObject(msalEntity); + JsonNode oldEntries = old.get(msalEntity); + JsonNode newEntries = update.get(msalEntity); if (oldEntries != null) { - Iterator> iterator = oldEntries.entrySet().iterator(); + Iterator> iterator = oldEntries.fields(); while (iterator.hasNext()) { - Map.Entry oEntry = iterator.next(); + Map.Entry oEntry = iterator.next(); String key = oEntry.getKey(); if (newEntries == null || !newEntries.has(key)) { @@ -135,15 +136,18 @@ public String serialize() { lock.readLock().lock(); try { if (!StringHelper.isBlank(serializedCachedSnapshot)) { - JsonObject cache = new JsonParser().parse(serializedCachedSnapshot).getAsJsonObject(); - JsonObject update = new Gson().toJsonTree(this).getAsJsonObject(); + JsonNode cache = JsonHelper.mapper.readTree(serializedCachedSnapshot); + JsonNode update = JsonHelper.mapper.valueToTree(this); mergeJsonObjects(cache, update); - return cache.toString(); + return JsonHelper.mapper.writeValueAsString(cache); } - return new GsonBuilder().create().toJson(this); - } finally { + return JsonHelper.mapper.writeValueAsString(this); + } + catch (JsonProcessingException e) { + throw new MsalClientException(e); + }finally { lock.readLock().unlock(); } } @@ -454,12 +458,16 @@ private boolean isMatchingScopes(AccessTokenCacheEntity accessTokenCacheEntity, ).findAny(); } - AuthenticationResult getCachedAuthenticationResult - (IAccount account, Authority authority, Set scopes, String clientId) { + AuthenticationResult getCachedAuthenticationResult( + IAccount account, + Authority authority, + Set scopes, + String clientId) { + AuthenticationResult.AuthenticationResultBuilder builder = AuthenticationResult.builder(); builder.environment(authority.host()); - Set environmentAliases = AadInstanceDiscovery.getAliases(account.environment()); + Set environmentAliases = AadInstanceDiscoveryProvider.getAliases(account.environment()); try (CacheAspect cacheAspect = new CacheAspect( TokenCacheAccessContext.builder(). @@ -517,11 +525,14 @@ private boolean isMatchingScopes(AccessTokenCacheEntity accessTokenCacheEntity, return builder.build(); } - AuthenticationResult getCachedAuthenticationResult - (Authority authority, Set scopes, String clientId) { + AuthenticationResult getCachedAuthenticationResult( + Authority authority, + Set scopes, + String clientId) { + AuthenticationResult.AuthenticationResultBuilder builder = AuthenticationResult.builder(); - Set environmentAliases = AadInstanceDiscovery.getAliases(authority.host); + Set environmentAliases = AadInstanceDiscoveryProvider.getAliases(authority.host); builder.environment(authority.host()); try (CacheAspect cacheAspect = new CacheAspect( diff --git a/src/main/java/com/microsoft/aad/msal4j/UserDiscoveryResponse.java b/src/main/java/com/microsoft/aad/msal4j/UserDiscoveryResponse.java index 26b15742..f4cbcdc6 100644 --- a/src/main/java/com/microsoft/aad/msal4j/UserDiscoveryResponse.java +++ b/src/main/java/com/microsoft/aad/msal4j/UserDiscoveryResponse.java @@ -3,7 +3,7 @@ package com.microsoft.aad.msal4j; -import com.google.gson.annotations.SerializedName; +import com.fasterxml.jackson.annotation.JsonProperty; import lombok.AccessLevel; import lombok.Getter; import lombok.experimental.Accessors; @@ -12,22 +12,22 @@ @Getter(AccessLevel.PACKAGE) class UserDiscoveryResponse { - @SerializedName("ver") + @JsonProperty("ver") private float version; - @SerializedName("account_type") + @JsonProperty("account_type") private String accountType; - @SerializedName("federation_metadata_url") + @JsonProperty("federation_metadata_url") private String federationMetadataUrl; - @SerializedName("federation_protocol") + @JsonProperty("federation_protocol") private String federationProtocol; - @SerializedName("federation_active_auth_url") + @JsonProperty("federation_active_auth_url") private String federationActiveAuthUrl; - @SerializedName("cloud_audience_urn") + @JsonProperty("cloud_audience_urn") private String cloudAudienceUrn; boolean isAccountFederated() { diff --git a/src/main/java/com/microsoft/aad/msal4j/WSTrustRequest.java b/src/main/java/com/microsoft/aad/msal4j/WSTrustRequest.java index adf9b8df..17be34fe 100644 --- a/src/main/java/com/microsoft/aad/msal4j/WSTrustRequest.java +++ b/src/main/java/com/microsoft/aad/msal4j/WSTrustRequest.java @@ -3,8 +3,6 @@ package com.microsoft.aad.msal4j; -import org.apache.commons.text.StringEscapeUtils; - import java.text.DateFormat; import java.text.SimpleDateFormat; import java.util.Date; @@ -186,6 +184,32 @@ static StringBuilder buildMessage(String address, String username, return messageBuilder; } + static String escapeXMLElementData(String data){ + StringBuilder sb = new StringBuilder(); + for(char ch : data.toCharArray()){ + switch (ch){ + case '<': + sb.append("<"); + break; + case '>': + sb.append(">"); + break; + case '\"': + sb.append("""); + break; + case '\'': + sb.append("'"); + break; + case '&': + sb.append("&"); + break; + default: + sb.append(ch); + } + } + return sb.toString(); + } + private static StringBuilder buildSecurityHeader( StringBuilder securityHeaderBuilder, String username, String password, WSTrustVersion version) { @@ -193,8 +217,8 @@ private static StringBuilder buildSecurityHeader( StringBuilder messageCredentialsBuilder = new StringBuilder( MAX_EXPECTED_MESSAGE_SIZE); String guid = UUID.randomUUID().toString(); - username = StringEscapeUtils.escapeXml10(username); - password = StringEscapeUtils.escapeXml10(password); + username = escapeXMLElementData(username); + password = escapeXMLElementData(password); DateFormat dateFormat = new SimpleDateFormat( "yyyy-MM-dd'T'HH:mm:ss'Z'", Locale.US); diff --git a/src/samples/msal-b2c-web-sample/pom.xml b/src/samples/msal-b2c-web-sample/pom.xml index d6255c87..ae544f7b 100644 --- a/src/samples/msal-b2c-web-sample/pom.xml +++ b/src/samples/msal-b2c-web-sample/pom.xml @@ -23,7 +23,7 @@ com.microsoft.azure msal4j - 1.2.0 + 1.3.0 com.nimbusds diff --git a/src/samples/msal-obo-sample/pom.xml b/src/samples/msal-obo-sample/pom.xml index ce0e3e5b..151c4d6d 100644 --- a/src/samples/msal-obo-sample/pom.xml +++ b/src/samples/msal-obo-sample/pom.xml @@ -23,7 +23,7 @@ com.microsoft.azure msal4j - 1.2.0 + 1.3.0 com.nimbusds diff --git a/src/samples/msal-web-sample/pom.xml b/src/samples/msal-web-sample/pom.xml index cf874d9b..947e215c 100644 --- a/src/samples/msal-web-sample/pom.xml +++ b/src/samples/msal-web-sample/pom.xml @@ -23,7 +23,7 @@ com.microsoft.azure msal4j - 1.2.0 + 1.3.0 com.nimbusds diff --git a/src/test/java/com/microsoft/aad/msal4j/AadInstanceDiscoveryTest.java b/src/test/java/com/microsoft/aad/msal4j/AadInstanceDiscoveryTest.java new file mode 100644 index 00000000..774a23b4 --- /dev/null +++ b/src/test/java/com/microsoft/aad/msal4j/AadInstanceDiscoveryTest.java @@ -0,0 +1,138 @@ +// Copyright (c) Microsoft Corporation. All rights reserved. +// Licensed under the MIT License. + +package com.microsoft.aad.msal4j; + +import org.powermock.api.easymock.PowerMock; +import org.powermock.core.classloader.annotations.PrepareForTest; +import org.powermock.modules.testng.PowerMockTestCase; +import org.testng.Assert; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +import java.net.URI; +import java.net.URL; + +@PrepareForTest(AadInstanceDiscoveryProvider.class) +public class AadInstanceDiscoveryTest extends PowerMockTestCase { + + @BeforeMethod + public void setup(){ + AadInstanceDiscoveryProvider.cache.clear(); + } + + @Test + public void aadInstanceDiscoveryTest_NotSetByDeveloper() throws Exception { + PublicClientApplication app = PublicClientApplication.builder("client_id") + .correlationId("correlation_id") + .authority("https://login.microsoftonline.com/my_tenant") + .build(); + + AuthorizationCodeParameters parameters = AuthorizationCodeParameters.builder( + "code", new URI("http://my.redirect.com")).build(); + + MsalRequest msalRequest = new AuthorizationCodeRequest( + parameters, + app, + new RequestContext(app, PublicApi.ACQUIRE_TOKEN_BY_AUTHORIZATION_CODE)); + + URL authority = new URL(app.authority()); + + String instanceDiscoveryData = TestHelper.readResource( + this.getClass(), + "/instance_discovery_data/aad_instance_discovery_response_valid.json"); + + AadInstanceDiscoveryResponse expectedResponse = JsonHelper.convertJsonToObject( + instanceDiscoveryData, + AadInstanceDiscoveryResponse.class); + + PowerMock.mockStaticPartial(AadInstanceDiscoveryProvider.class, "sendInstanceDiscoveryRequest"); + + PowerMock.expectPrivate( + AadInstanceDiscoveryProvider.class, + "sendInstanceDiscoveryRequest", + authority, + msalRequest, + app.getServiceBundle()).andReturn(expectedResponse); + + PowerMock.replay(AadInstanceDiscoveryProvider.class); + + InstanceDiscoveryMetadataEntry entry = AadInstanceDiscoveryProvider.getMetadataEntry( + new URL(app.authority()), + false, + msalRequest, + app.getServiceBundle()); + + PowerMock.verify(AadInstanceDiscoveryProvider.class); + + Assert.assertEquals(entry.preferredNetwork(), "login.microsoftonline.com"); + Assert.assertEquals(entry.preferredCache(), "login.windows.net"); + Assert.assertEquals(entry.aliases().size(), 4); + Assert.assertTrue(entry.aliases().contains("login.microsoftonline.com")); + Assert.assertTrue(entry.aliases().contains("login.windows.net")); + Assert.assertTrue(entry.aliases().contains("login.microsoft.com")); + Assert.assertTrue(entry.aliases().contains("sts.windows.net")); + } + + @Test + public void aadInstanceDiscoveryTest_responseSetByDeveloper_validResponse() throws Exception{ + + String instanceDiscoveryResponse = TestHelper.readResource( + this.getClass(), + "/instance_discovery_data/aad_instance_discovery_response_valid.json"); + + PublicClientApplication app = PublicClientApplication.builder("client_id") + .aadInstanceDiscoveryResponse(instanceDiscoveryResponse) + .build(); + + AuthorizationCodeParameters parameters = AuthorizationCodeParameters.builder( + "code", new URI("http://my.redirect.com")).build(); + + MsalRequest msalRequest = new AuthorizationCodeRequest( + parameters, + app, + new RequestContext(app, PublicApi.ACQUIRE_TOKEN_BY_AUTHORIZATION_CODE)); + + URL authority = new URL(app.authority()); + + PowerMock.mockStaticPartial( + AadInstanceDiscoveryProvider.class, + "sendInstanceDiscoveryRequest"); + + // throw exception if we try to get metadata from network. + PowerMock.expectPrivate( + AadInstanceDiscoveryProvider.class, + "sendInstanceDiscoveryRequest", + authority, + msalRequest, + app.getServiceBundle()).andThrow(new AssertionError()).anyTimes(); + + PowerMock.replay(AadInstanceDiscoveryProvider.class); + + InstanceDiscoveryMetadataEntry entry = AadInstanceDiscoveryProvider.getMetadataEntry( + authority, + false, + msalRequest, + app.getServiceBundle()); + + Assert.assertEquals(entry.preferredNetwork(), "login.microsoftonline.com"); + Assert.assertEquals(entry.preferredCache(), "login.windows.net"); + Assert.assertEquals(entry.aliases().size(), 4); + Assert.assertTrue(entry.aliases().contains("login.microsoftonline.com")); + Assert.assertTrue(entry.aliases().contains("login.windows.net")); + Assert.assertTrue(entry.aliases().contains("login.microsoft.com")); + Assert.assertTrue(entry.aliases().contains("sts.windows.net")); + } + + @Test(expectedExceptions = MsalClientException.class) + public void aadInstanceDiscoveryTest_responseSetByDeveloper_invalidJson() throws Exception{ + + String instanceDiscoveryResponse = TestHelper.readResource( + this.getClass(), + "/instance_discovery_data/aad_instance_discovery_response_invalid_json.json"); + + PublicClientApplication app = PublicClientApplication.builder("client_id") + .aadInstanceDiscoveryResponse(instanceDiscoveryResponse) + .build(); + } +} diff --git a/src/test/java/com/microsoft/aad/msal4j/AuthorityTest.java b/src/test/java/com/microsoft/aad/msal4j/AuthorityTest.java index a19830be..91717e94 100644 --- a/src/test/java/com/microsoft/aad/msal4j/AuthorityTest.java +++ b/src/test/java/com/microsoft/aad/msal4j/AuthorityTest.java @@ -12,7 +12,7 @@ @Test(groups = { "checkin" }) @PrepareForTest({ AADAuthority.class, HttpHelper.class, - JsonHelper.class, InstanceDiscoveryResponse.class }) + JsonHelper.class, AadInstanceDiscoveryResponse.class }) public class AuthorityTest extends AbstractMsalTests { @Test diff --git a/src/test/java/com/microsoft/aad/msal4j/CacheFormatTests.java b/src/test/java/com/microsoft/aad/msal4j/CacheFormatTests.java index b3ce00d2..06bb84b4 100644 --- a/src/test/java/com/microsoft/aad/msal4j/CacheFormatTests.java +++ b/src/test/java/com/microsoft/aad/msal4j/CacheFormatTests.java @@ -3,7 +3,8 @@ package com.microsoft.aad.msal4j; -import com.google.gson.GsonBuilder; +import com.fasterxml.jackson.databind.DeserializationFeature; +import com.fasterxml.jackson.databind.ObjectMapper; import com.nimbusds.oauth2.sdk.ParseException; import com.nimbusds.oauth2.sdk.http.HTTPResponse; import com.nimbusds.oauth2.sdk.util.JSONObjectUtils; @@ -24,6 +25,9 @@ import java.net.URL; import java.nio.file.Files; import java.nio.file.Paths; +import java.sql.Time; +import java.time.Duration; +import java.time.Instant; import java.util.*; public class CacheFormatTests extends AbstractMsalTests { @@ -187,7 +191,7 @@ private void validateAccessTokenCacheEntity(String folder, String tokenResponse, String keyExpected = readResource(folder + AT_CACHE_ENTITY_KEY); Assert.assertEquals(keyActual, keyExpected); - String valueActual = new GsonBuilder().create().toJson(tokenCache.accessTokens.get(keyActual)); + String valueActual = JsonHelper.mapper.writeValueAsString(tokenCache.accessTokens.get(keyActual)); String valueExpected = readResource(folder + AT_CACHE_ENTITY); JSONObject tokenResponseJsonObj = JSONObjectUtils.parse(tokenResponse); @@ -207,7 +211,7 @@ private void validateRefreshTokenCacheEntity(String folder, TokenCache tokenCach String keyExpected = readResource(folder + RT_CACHE_ENTITY_KEY); Assert.assertEquals(actualKey, keyExpected); - String actualValue = new GsonBuilder().create().toJson(tokenCache.refreshTokens.get(actualKey)); + String actualValue = JsonHelper.mapper.writeValueAsString(tokenCache.refreshTokens.get(actualKey)); String valueExpected = readResource(folder + RT_CACHE_ENTITY); JSONAssert.assertEquals(valueExpected, actualValue, JSONCompareMode.STRICT); } @@ -243,7 +247,7 @@ private void validateIdTokenCacheEntity(String folder, TokenCache tokenCache) String keyExpected = readResource(folder + ID_TOKEN_CACHE_ENTITY_KEY); Assert.assertEquals(actualKey, keyExpected); - String actualValue = new GsonBuilder().create().toJson(tokenCache.idTokens.get(actualKey)); + String actualValue = JsonHelper.mapper.writeValueAsString(tokenCache.idTokens.get(actualKey)); String valueExpected = readResource(folder + ID_TOKEN_CACHE_ENTITY); JSONAssert.assertEquals(valueExpected, actualValue, new IdTokenComparator(JSONCompareMode.STRICT, folder)); @@ -258,7 +262,7 @@ private void validateAccountCacheEntity(String folder, TokenCache tokenCache) String keyExpected = readResource(folder + ACCOUNT_CACHE_ENTITY_KEY); Assert.assertEquals(actualKey, keyExpected); - String actualValue = new GsonBuilder().create().toJson(tokenCache.accounts.get(actualKey)); + String actualValue = JsonHelper.mapper.writeValueAsString(tokenCache.accounts.get(actualKey)); String valueExpected = readResource(folder + ACCOUNT_CACHE_ENTITY); JSONAssert.assertEquals(valueExpected, actualValue, JSONCompareMode.STRICT); @@ -277,7 +281,7 @@ private void validateAppMetadataCacheEntity(String folder, TokenCache tokenCache String keyExpected = readResource(folder + APP_METADATA_ENTITY_KEY); Assert.assertEquals(actualKey, keyExpected); - String actualValue = new GsonBuilder().create().toJson(tokenCache.appMetadata.get(actualKey)); + String actualValue = JsonHelper.mapper.writeValueAsString(tokenCache.appMetadata.get(actualKey)); String valueExpected = readResource(folder + APP_METADATA_CACHE_ENTITY); JSONAssert.assertEquals(valueExpected, actualValue, JSONCompareMode.STRICT); diff --git a/src/test/java/com/microsoft/aad/msal4j/WSTrustRequestTest.java b/src/test/java/com/microsoft/aad/msal4j/WSTrustRequestTest.java index 1b90e9b6..cca651b9 100644 --- a/src/test/java/com/microsoft/aad/msal4j/WSTrustRequestTest.java +++ b/src/test/java/com/microsoft/aad/msal4j/WSTrustRequestTest.java @@ -3,6 +3,7 @@ package com.microsoft.aad.msal4j; +import org.apache.commons.text.StringEscapeUtils; import org.testng.Assert; import org.testng.annotations.Test; @@ -41,4 +42,13 @@ public void buildMessage_integrated() throws Exception { Assert.assertTrue(msg.contains("cloudAudienceUrn")); Assert.assertTrue(!msg.contains("