diff --git a/.gitignore b/.gitignore index 24effb44b..0e54e127f 100644 --- a/.gitignore +++ b/.gitignore @@ -75,3 +75,4 @@ env.properties /hercules/config.json gradle.properties +center/appsettings.json diff --git a/azure-pipelines-ci.yml b/azure-pipelines-ci.yml index 1a06c4291..ad07682fd 100644 --- a/azure-pipelines-ci.yml +++ b/azure-pipelines-ci.yml @@ -149,7 +149,7 @@ stages: script: | cp android_client/app/build/outputs/apk/release/app-release.apk common/src/main/resources/record_release.apk -force - task: Gradle@2 - displayName: Build center (with MISE) + displayName: Build center inputs: gradleWrapperFile: 'gradlew' tasks: 'center:bootJar --stacktrace' @@ -158,7 +158,6 @@ stages: jdkVersionOption: '1.11' sonarQubeRunAnalysis: false spotBugsAnalysis: false - options: '-PIDDPUsername=IdentityDivision -PIDDPPassword=$(IDDPPassword) -PenableMISE=true' condition: eq(variables.fullBuild, 'true') - task: Gradle@2 displayName: Build agent @@ -207,23 +206,6 @@ stages: Contents: '*.jar' TargetFolder: '$(Build.ArtifactStagingDirectory)/center_deploy' condition: eq(variables.fullBuild, 'true') - - task: Gradle@2 - displayName: Build center (without MISE) - inputs: - gradleWrapperFile: 'gradlew' - tasks: 'center:bootJar --stacktrace' - publishJUnitResults: false - javaHomeOption: 'JDKVersion' - jdkVersionOption: '1.11' - sonarQubeRunAnalysis: false - spotBugsAnalysis: false - - task: CopyFiles@2 - displayName: Copy center jar - inputs: - SourceFolder: 'center/build/libs/' - Contents: '*.jar' - TargetFolder: '$(Build.ArtifactStagingDirectory)/center_publish' - condition: eq(variables.fullBuild, 'true') - task: Gradle@2 displayName: Package Mac installer inputs: diff --git a/center/Dockerfile_MISE b/center/Dockerfile_MISE new file mode 100644 index 000000000..5c7b85ea7 --- /dev/null +++ b/center/Dockerfile_MISE @@ -0,0 +1,7 @@ +# run this after: ./gradlew.bat :bootJar +FROM hydralabreistry.azurecr.io/mise-1p-container-image:1.36.0-mise-azurelinux3.0-distroless + +ARG CONFIG_FILE=appsettings.json +COPY ${CONFIG_FILE} /app/appsettings.json + +ENTRYPOINT ["dotnet", "Microsoft.Identity.ServiceEssentials.Container.dll"] diff --git a/center/build.gradle b/center/build.gradle index ebf138b90..5bd640792 100644 --- a/center/build.gradle +++ b/center/build.gradle @@ -14,16 +14,6 @@ bootJar.dependsOn("checkstyleMain") repositories { mavenCentral() - if (project.hasProperty("enableMISE")) { - maven { - url 'https://identitydivision.pkgs.visualstudio.com/_packaging/IDDP/maven/v1' - name 'IDDP' - credentials(PasswordCredentials) - authentication { - basic(BasicAuthentication) - } - } - } } bootJar { @@ -77,9 +67,6 @@ dependencies { compileOnly 'org.projectlombok:lombok:1.18.20' annotationProcessor 'org.projectlombok:lombok:1.18.20' - if (project.hasProperty("enableMISE")) { - compile(group: 'com.microsoft.identity.service.essentials', name: 'java-adapter', version: '1.32.0') - } } import org.apache.tools.ant.taskdefs.condition.Os diff --git a/center/src/main/java/com/microsoft/hydralab/center/util/AuthUtil.java b/center/src/main/java/com/microsoft/hydralab/center/util/AuthUtil.java index 32b5c798d..eda0bdbf8 100644 --- a/center/src/main/java/com/microsoft/hydralab/center/util/AuthUtil.java +++ b/center/src/main/java/com/microsoft/hydralab/center/util/AuthUtil.java @@ -63,14 +63,16 @@ public class AuthUtil { String scope; @Value("${spring.security.oauth2.client.provider.azure-ad.mise-enabled:false}") boolean miseEnabled; + @Value("${spring.security.oauth2.client.provider.azure-ad.mise-endpoint:}") + String miseEndpoint; Map urlMapping = null; private static final org.slf4j.Logger LOGGER = LoggerFactory.getLogger(AuthUtil.class); public boolean isValidToken(String token) { LOGGER.info("Starting token validation..."); - if (miseEnabled) { - return validateTokenWithMISE(token); + if (miseEnabled && miseEndpoint != null && !miseEndpoint.isEmpty()) { + return validateTokenWithMISEEndpoint(token); } return validateTokenWithPublicKey(token) && validateAudienceAndExpiredTime(token); } @@ -118,91 +120,117 @@ private boolean validateTokenWithPublicKey(String token) { } } - private boolean validateTokenWithMISE(String token) { + private boolean validateTokenWithMISEEndpoint(String token) { LOGGER.info("Starting MISE token validation..."); - + // call MISE endpoint to validate the token try { - // Mise mise = Mise.createClient(); - Class miseClass = Class.forName("com.microsoft.identity.service.essentials.Mise"); - Object mise = miseClass.getMethod("createClient").invoke(null); - - LOGGER.info("Initializing MISE..."); - // mise.assignLogMessageCallback(new Mise.ILogCallback() {...}, null); - Class iLogCallbackClass = Class.forName("com.microsoft.identity.service.essentials.Mise$ILogCallback"); - - Object logCallback = java.lang.reflect.Proxy.newProxyInstance( - iLogCallbackClass.getClassLoader(), - new Class[]{iLogCallbackClass}, - (proxy, method, args) -> { - String methodName = method.getName(); - if ("callback".equals(methodName)) { - Object level = args[0]; - String message = (String) args[1]; - // Print all log levels for simplicity - LOGGER.info(message); - } - return null; - } - ); - - miseClass.getMethod("assignLogMessageCallback", iLogCallbackClass, Object.class) - .invoke(mise, logCallback, null); - // Configure MISE - JSONObject config = new JSONObject(); - JSONObject azureAd = new JSONObject(); - azureAd.put("Instance", instanceUri); - azureAd.put("ClientId", clientId); - azureAd.put("TenantId", tenantId); - String[] audiences = audience.split(","); - azureAd.put("Audiences", audiences); - JSONObject logging = new JSONObject(); - logging.put("logLevel", "Debug"); - azureAd.put("Logging", logging); - config.put("AzureAd", azureAd); - - miseClass.getMethod("configure", String.class, String.class) - .invoke(mise, config.toString(), null); - - // MiseValidationInput miseValidationInput = new MiseValidationInput(); - Class miseValidationInputClass = Class.forName("com.microsoft.identity.service.essentials.MiseValidationInput"); - Object miseValidationInput = miseValidationInputClass.getDeclaredConstructor().newInstance(); - - miseValidationInputClass.getField("authorizationHeader").set(miseValidationInput, "Bearer " + token); - miseValidationInputClass.getField("originalMethodHeader").set(miseValidationInput, "GET"); - miseValidationInputClass.getField("originalUriHeader").set(miseValidationInput, "https://myapi.com/api/values"); - - // try (MiseValidationResult validationResult = mise.validate(miseValidationInput)) { ... } - Object validationResult = miseClass.getMethod("validate", miseValidationInputClass) - .invoke(mise, miseValidationInput); - - // mise.unassignLogMessageCallback(); - miseClass.getMethod("unassignLogMessageCallback") - .invoke(mise); - - Class miseValidationResultClass = Class.forName("com.microsoft.identity.service.essentials.MiseValidationResult"); - int statusCode = (int) miseValidationResultClass.getMethod("getHttpResponseStatusCode").invoke(validationResult); - LOGGER.info("Status code " + statusCode); - - String errorDescription = (String) miseValidationResultClass.getMethod("getErrorDescription").invoke(validationResult); - if (errorDescription != null) { - LOGGER.error("Error message " + errorDescription); - } - // Close validationResult if AutoCloseable - if (validationResult instanceof AutoCloseable) { - ((AutoCloseable) validationResult).close(); - } - if (statusCode != 200) { - LOGGER.error("MISE token validation failed with status code: " + statusCode); + RestTemplate restTemplateHttps = new RestTemplate(RestTemplateConfig.generateHttpRequestFactory()); + HttpHeaders headers = new HttpHeaders(); + headers.add("Content-Type", "application/json"); + headers.add("Authorization", "Bearer " + token); + headers.add("Original-Uri", "https://myapi.com/api/values"); + headers.add("Original-Method", "GET"); + headers.add("X-Forwarded-For", "1.2.3.4"); + HttpEntity entity = new HttpEntity<>(headers); + ResponseEntity response = restTemplateHttps.exchange(miseEndpoint, HttpMethod.POST, entity, JSONObject.class); + if (response.getStatusCode().is2xxSuccessful()) { + LOGGER.info("MISE token validation passed"); + return true; + } else { + LOGGER.error("MISE token validation failed with status code: " + response.getStatusCodeValue()); return false; } - LOGGER.info("MISE token validation passed"); } catch (Exception e) { - e.printStackTrace(); + LOGGER.error("MISE token validation failed: " + e.getMessage()); return false; } - return true; } +// private boolean validateTokenWithMISE(String token) { +// LOGGER.info("Starting MISE token validation..."); +// +// try { +// // Mise mise = Mise.createClient(); +// Class miseClass = Class.forName("com.microsoft.identity.service.essentials.Mise"); +// Object mise = miseClass.getMethod("createClient").invoke(null); +// +// LOGGER.info("Initializing MISE..."); +// // mise.assignLogMessageCallback(new Mise.ILogCallback() {...}, null); +// Class iLogCallbackClass = Class.forName("com.microsoft.identity.service.essentials.Mise$ILogCallback"); +// +// Object logCallback = java.lang.reflect.Proxy.newProxyInstance( +// iLogCallbackClass.getClassLoader(), +// new Class[]{iLogCallbackClass}, +// (proxy, method, args) -> { +// String methodName = method.getName(); +// if ("callback".equals(methodName)) { +// Object level = args[0]; +// String message = (String) args[1]; +// // Print all log levels for simplicity +// LOGGER.info(message); +// } +// return null; +// } +// ); +// +// miseClass.getMethod("assignLogMessageCallback", iLogCallbackClass, Object.class) +// .invoke(mise, logCallback, null); +// // Configure MISE +// JSONObject config = new JSONObject(); +// JSONObject azureAd = new JSONObject(); +// azureAd.put("Instance", instanceUri); +// azureAd.put("ClientId", clientId); +// azureAd.put("TenantId", tenantId); +// String[] audiences = audience.split(","); +// azureAd.put("Audiences", audiences); +// JSONObject logging = new JSONObject(); +// logging.put("logLevel", "Debug"); +// azureAd.put("Logging", logging); +// config.put("AzureAd", azureAd); +// +// miseClass.getMethod("configure", String.class, String.class) +// .invoke(mise, config.toString(), null); +// +// // MiseValidationInput miseValidationInput = new MiseValidationInput(); +// Class miseValidationInputClass = Class.forName("com.microsoft.identity.service.essentials.MiseValidationInput"); +// Object miseValidationInput = miseValidationInputClass.getDeclaredConstructor().newInstance(); +// +// miseValidationInputClass.getField("authorizationHeader").set(miseValidationInput, "Bearer " + token); +// miseValidationInputClass.getField("originalMethodHeader").set(miseValidationInput, "GET"); +// miseValidationInputClass.getField("originalUriHeader").set(miseValidationInput, "https://myapi.com/api/values"); +// +// // try (MiseValidationResult validationResult = mise.validate(miseValidationInput)) { ... } +// Object validationResult = miseClass.getMethod("validate", miseValidationInputClass) +// .invoke(mise, miseValidationInput); +// +// // mise.unassignLogMessageCallback(); +// miseClass.getMethod("unassignLogMessageCallback") +// .invoke(mise); +// +// Class miseValidationResultClass = Class.forName("com.microsoft.identity.service.essentials.MiseValidationResult"); +// int statusCode = (int) miseValidationResultClass.getMethod("getHttpResponseStatusCode").invoke(validationResult); +// LOGGER.info("Status code " + statusCode); +// +// String errorDescription = (String) miseValidationResultClass.getMethod("getErrorDescription").invoke(validationResult); +// if (errorDescription != null) { +// LOGGER.error("Error message " + errorDescription); +// } +// // Close validationResult if AutoCloseable +// if (validationResult instanceof AutoCloseable) { +// ((AutoCloseable) validationResult).close(); +// } +// if (statusCode != 200) { +// LOGGER.error("MISE token validation failed with status code: " + statusCode); +// return false; +// } +// LOGGER.info("MISE token validation passed"); +// } catch (Exception e) { +// e.printStackTrace(); +// return false; +// } +// return true; +// } + private PublicKey getPublicKey(JWSObject jwsObject, JWKSet jwkSet) throws JOSEException { JWSAlgorithm algorithm = jwsObject.getHeader().getAlgorithm(); if (!algorithm.equals(JWSAlgorithm.RS256)) { diff --git a/center/src/main/resources/application-release.yml b/center/src/main/resources/application-release.yml index 4bf382daf..469d6f8b5 100644 --- a/center/src/main/resources/application-release.yml +++ b/center/src/main/resources/application-release.yml @@ -13,6 +13,7 @@ spring: audience: ${MICROSOFT_PROVIDER_AUDIENCE} instance-uri: ${MICROSOFT_PROVIDER_INSTANCE_URI} mise-enabled: ${MICROSOFT_MISE_ENABLED:false} + mise-endpoint: ${MICROSOFT_MISE_ENDPOINT} registration: azure-client: provider: azure-ad