Skip to content

Commit 2c9444c

Browse files
authored
Fature: adapt to MISE container mode (#711)
<!-- Please provide brief information about the PR, what it contains & its purpose, new behaviors after the change. And let us know here if you need any help: https://github.com/microsoft/HydraLab/issues/new --> ## Description <!-- A few words to explain your changes --> ### Linked GitHub issue ID: # ## Pull Request Checklist <!-- Put an x in the boxes that apply. This is simply a reminder of what we are going to look for before merging your code. --> - [ ] Tests for the changes have been added (for bug fixes / features) - [x] Code compiles correctly with all tests are passed. - [x] I've read the [contributing guide](https://github.com/microsoft/HydraLab/blob/main/CONTRIBUTING.md#making-changes-to-the-code) and followed the recommended practices. - [ ] [Wikis](https://github.com/microsoft/HydraLab/wiki) or [README](https://github.com/microsoft/HydraLab/blob/main/README.md) have been reviewed and added / updated if needed (for bug fixes / features) ### Does this introduce a breaking change? *If this introduces a breaking change for Hydra Lab users, please describe the impact and migration path.* - [ ] Yes - [x] No ## How you tested it *Please make sure the change is tested, you can test it by adding UTs, do local test and share the screenshots, etc.* Please check the type of change your PR introduces: - [ ] Bugfix - [x] Feature - [ ] Technical design - [ ] Build related changes - [ ] Refactoring (no functional changes, no api changes) - [ ] Code style update (formatting, renaming) or Documentation content changes - [ ] Other (please describe): ### Feature UI screenshots or Technical design diagrams *If this is a relatively large or complex change, kick it off by drawing the tech design with PlantUML and explaining why you chose the solution you did and what alternatives you considered, etc...*
1 parent 99f847f commit 2c9444c

File tree

6 files changed

+116
-110
lines changed

6 files changed

+116
-110
lines changed

.gitignore

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -75,3 +75,4 @@ env.properties
7575

7676
/hercules/config.json
7777
gradle.properties
78+
center/appsettings.json

azure-pipelines-ci.yml

Lines changed: 1 addition & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -149,7 +149,7 @@ stages:
149149
script: |
150150
cp android_client/app/build/outputs/apk/release/app-release.apk common/src/main/resources/record_release.apk -force
151151
- task: Gradle@2
152-
displayName: Build center (with MISE)
152+
displayName: Build center
153153
inputs:
154154
gradleWrapperFile: 'gradlew'
155155
tasks: 'center:bootJar --stacktrace'
@@ -158,7 +158,6 @@ stages:
158158
jdkVersionOption: '1.11'
159159
sonarQubeRunAnalysis: false
160160
spotBugsAnalysis: false
161-
options: '-PIDDPUsername=IdentityDivision -PIDDPPassword=$(IDDPPassword) -PenableMISE=true'
162161
condition: eq(variables.fullBuild, 'true')
163162
- task: Gradle@2
164163
displayName: Build agent
@@ -207,23 +206,6 @@ stages:
207206
Contents: '*.jar'
208207
TargetFolder: '$(Build.ArtifactStagingDirectory)/center_deploy'
209208
condition: eq(variables.fullBuild, 'true')
210-
- task: Gradle@2
211-
displayName: Build center (without MISE)
212-
inputs:
213-
gradleWrapperFile: 'gradlew'
214-
tasks: 'center:bootJar --stacktrace'
215-
publishJUnitResults: false
216-
javaHomeOption: 'JDKVersion'
217-
jdkVersionOption: '1.11'
218-
sonarQubeRunAnalysis: false
219-
spotBugsAnalysis: false
220-
- task: CopyFiles@2
221-
displayName: Copy center jar
222-
inputs:
223-
SourceFolder: 'center/build/libs/'
224-
Contents: '*.jar'
225-
TargetFolder: '$(Build.ArtifactStagingDirectory)/center_publish'
226-
condition: eq(variables.fullBuild, 'true')
227209
- task: Gradle@2
228210
displayName: Package Mac installer
229211
inputs:

center/Dockerfile_MISE

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
# run this after: ./gradlew.bat :bootJar
2+
FROM hydralabreistry.azurecr.io/mise-1p-container-image:1.36.0-mise-azurelinux3.0-distroless
3+
4+
ARG CONFIG_FILE=appsettings.json
5+
COPY ${CONFIG_FILE} /app/appsettings.json
6+
7+
ENTRYPOINT ["dotnet", "Microsoft.Identity.ServiceEssentials.Container.dll"]

center/build.gradle

Lines changed: 0 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -14,16 +14,6 @@ bootJar.dependsOn("checkstyleMain")
1414

1515
repositories {
1616
mavenCentral()
17-
if (project.hasProperty("enableMISE")) {
18-
maven {
19-
url 'https://identitydivision.pkgs.visualstudio.com/_packaging/IDDP/maven/v1'
20-
name 'IDDP'
21-
credentials(PasswordCredentials)
22-
authentication {
23-
basic(BasicAuthentication)
24-
}
25-
}
26-
}
2717
}
2818

2919
bootJar {
@@ -77,9 +67,6 @@ dependencies {
7767

7868
compileOnly 'org.projectlombok:lombok:1.18.20'
7969
annotationProcessor 'org.projectlombok:lombok:1.18.20'
80-
if (project.hasProperty("enableMISE")) {
81-
compile(group: 'com.microsoft.identity.service.essentials', name: 'java-adapter', version: '1.32.0')
82-
}
8370
}
8471

8572
import org.apache.tools.ant.taskdefs.condition.Os

center/src/main/java/com/microsoft/hydralab/center/util/AuthUtil.java

Lines changed: 106 additions & 78 deletions
Original file line numberDiff line numberDiff line change
@@ -63,14 +63,16 @@ public class AuthUtil {
6363
String scope;
6464
@Value("${spring.security.oauth2.client.provider.azure-ad.mise-enabled:false}")
6565
boolean miseEnabled;
66+
@Value("${spring.security.oauth2.client.provider.azure-ad.mise-endpoint:}")
67+
String miseEndpoint;
6668

6769
Map<String, Boolean> urlMapping = null;
6870
private static final org.slf4j.Logger LOGGER = LoggerFactory.getLogger(AuthUtil.class);
6971

7072
public boolean isValidToken(String token) {
7173
LOGGER.info("Starting token validation...");
72-
if (miseEnabled) {
73-
return validateTokenWithMISE(token);
74+
if (miseEnabled && miseEndpoint != null && !miseEndpoint.isEmpty()) {
75+
return validateTokenWithMISEEndpoint(token);
7476
}
7577
return validateTokenWithPublicKey(token) && validateAudienceAndExpiredTime(token);
7678
}
@@ -118,91 +120,117 @@ private boolean validateTokenWithPublicKey(String token) {
118120
}
119121
}
120122

121-
private boolean validateTokenWithMISE(String token) {
123+
private boolean validateTokenWithMISEEndpoint(String token) {
122124
LOGGER.info("Starting MISE token validation...");
123-
125+
// call MISE endpoint to validate the token
124126
try {
125-
// Mise mise = Mise.createClient();
126-
Class<?> miseClass = Class.forName("com.microsoft.identity.service.essentials.Mise");
127-
Object mise = miseClass.getMethod("createClient").invoke(null);
128-
129-
LOGGER.info("Initializing MISE...");
130-
// mise.assignLogMessageCallback(new Mise.ILogCallback() {...}, null);
131-
Class<?> iLogCallbackClass = Class.forName("com.microsoft.identity.service.essentials.Mise$ILogCallback");
132-
133-
Object logCallback = java.lang.reflect.Proxy.newProxyInstance(
134-
iLogCallbackClass.getClassLoader(),
135-
new Class<?>[]{iLogCallbackClass},
136-
(proxy, method, args) -> {
137-
String methodName = method.getName();
138-
if ("callback".equals(methodName)) {
139-
Object level = args[0];
140-
String message = (String) args[1];
141-
// Print all log levels for simplicity
142-
LOGGER.info(message);
143-
}
144-
return null;
145-
}
146-
);
147-
148-
miseClass.getMethod("assignLogMessageCallback", iLogCallbackClass, Object.class)
149-
.invoke(mise, logCallback, null);
150-
// Configure MISE
151-
JSONObject config = new JSONObject();
152-
JSONObject azureAd = new JSONObject();
153-
azureAd.put("Instance", instanceUri);
154-
azureAd.put("ClientId", clientId);
155-
azureAd.put("TenantId", tenantId);
156-
String[] audiences = audience.split(",");
157-
azureAd.put("Audiences", audiences);
158-
JSONObject logging = new JSONObject();
159-
logging.put("logLevel", "Debug");
160-
azureAd.put("Logging", logging);
161-
config.put("AzureAd", azureAd);
162-
163-
miseClass.getMethod("configure", String.class, String.class)
164-
.invoke(mise, config.toString(), null);
165-
166-
// MiseValidationInput miseValidationInput = new MiseValidationInput();
167-
Class<?> miseValidationInputClass = Class.forName("com.microsoft.identity.service.essentials.MiseValidationInput");
168-
Object miseValidationInput = miseValidationInputClass.getDeclaredConstructor().newInstance();
169-
170-
miseValidationInputClass.getField("authorizationHeader").set(miseValidationInput, "Bearer " + token);
171-
miseValidationInputClass.getField("originalMethodHeader").set(miseValidationInput, "GET");
172-
miseValidationInputClass.getField("originalUriHeader").set(miseValidationInput, "https://myapi.com/api/values");
173-
174-
// try (MiseValidationResult validationResult = mise.validate(miseValidationInput)) { ... }
175-
Object validationResult = miseClass.getMethod("validate", miseValidationInputClass)
176-
.invoke(mise, miseValidationInput);
177-
178-
// mise.unassignLogMessageCallback();
179-
miseClass.getMethod("unassignLogMessageCallback")
180-
.invoke(mise);
181-
182-
Class<?> miseValidationResultClass = Class.forName("com.microsoft.identity.service.essentials.MiseValidationResult");
183-
int statusCode = (int) miseValidationResultClass.getMethod("getHttpResponseStatusCode").invoke(validationResult);
184-
LOGGER.info("Status code " + statusCode);
185-
186-
String errorDescription = (String) miseValidationResultClass.getMethod("getErrorDescription").invoke(validationResult);
187-
if (errorDescription != null) {
188-
LOGGER.error("Error message " + errorDescription);
189-
}
190-
// Close validationResult if AutoCloseable
191-
if (validationResult instanceof AutoCloseable) {
192-
((AutoCloseable) validationResult).close();
193-
}
194-
if (statusCode != 200) {
195-
LOGGER.error("MISE token validation failed with status code: " + statusCode);
127+
RestTemplate restTemplateHttps = new RestTemplate(RestTemplateConfig.generateHttpRequestFactory());
128+
HttpHeaders headers = new HttpHeaders();
129+
headers.add("Content-Type", "application/json");
130+
headers.add("Authorization", "Bearer " + token);
131+
headers.add("Original-Uri", "https://myapi.com/api/values");
132+
headers.add("Original-Method", "GET");
133+
headers.add("X-Forwarded-For", "1.2.3.4");
134+
HttpEntity<HttpHeaders> entity = new HttpEntity<>(headers);
135+
ResponseEntity<JSONObject> response = restTemplateHttps.exchange(miseEndpoint, HttpMethod.POST, entity, JSONObject.class);
136+
if (response.getStatusCode().is2xxSuccessful()) {
137+
LOGGER.info("MISE token validation passed");
138+
return true;
139+
} else {
140+
LOGGER.error("MISE token validation failed with status code: " + response.getStatusCodeValue());
196141
return false;
197142
}
198-
LOGGER.info("MISE token validation passed");
199143
} catch (Exception e) {
200-
e.printStackTrace();
144+
LOGGER.error("MISE token validation failed: " + e.getMessage());
201145
return false;
202146
}
203-
return true;
204147
}
205148

149+
// private boolean validateTokenWithMISE(String token) {
150+
// LOGGER.info("Starting MISE token validation...");
151+
//
152+
// try {
153+
// // Mise mise = Mise.createClient();
154+
// Class<?> miseClass = Class.forName("com.microsoft.identity.service.essentials.Mise");
155+
// Object mise = miseClass.getMethod("createClient").invoke(null);
156+
//
157+
// LOGGER.info("Initializing MISE...");
158+
// // mise.assignLogMessageCallback(new Mise.ILogCallback() {...}, null);
159+
// Class<?> iLogCallbackClass = Class.forName("com.microsoft.identity.service.essentials.Mise$ILogCallback");
160+
//
161+
// Object logCallback = java.lang.reflect.Proxy.newProxyInstance(
162+
// iLogCallbackClass.getClassLoader(),
163+
// new Class<?>[]{iLogCallbackClass},
164+
// (proxy, method, args) -> {
165+
// String methodName = method.getName();
166+
// if ("callback".equals(methodName)) {
167+
// Object level = args[0];
168+
// String message = (String) args[1];
169+
// // Print all log levels for simplicity
170+
// LOGGER.info(message);
171+
// }
172+
// return null;
173+
// }
174+
// );
175+
//
176+
// miseClass.getMethod("assignLogMessageCallback", iLogCallbackClass, Object.class)
177+
// .invoke(mise, logCallback, null);
178+
// // Configure MISE
179+
// JSONObject config = new JSONObject();
180+
// JSONObject azureAd = new JSONObject();
181+
// azureAd.put("Instance", instanceUri);
182+
// azureAd.put("ClientId", clientId);
183+
// azureAd.put("TenantId", tenantId);
184+
// String[] audiences = audience.split(",");
185+
// azureAd.put("Audiences", audiences);
186+
// JSONObject logging = new JSONObject();
187+
// logging.put("logLevel", "Debug");
188+
// azureAd.put("Logging", logging);
189+
// config.put("AzureAd", azureAd);
190+
//
191+
// miseClass.getMethod("configure", String.class, String.class)
192+
// .invoke(mise, config.toString(), null);
193+
//
194+
// // MiseValidationInput miseValidationInput = new MiseValidationInput();
195+
// Class<?> miseValidationInputClass = Class.forName("com.microsoft.identity.service.essentials.MiseValidationInput");
196+
// Object miseValidationInput = miseValidationInputClass.getDeclaredConstructor().newInstance();
197+
//
198+
// miseValidationInputClass.getField("authorizationHeader").set(miseValidationInput, "Bearer " + token);
199+
// miseValidationInputClass.getField("originalMethodHeader").set(miseValidationInput, "GET");
200+
// miseValidationInputClass.getField("originalUriHeader").set(miseValidationInput, "https://myapi.com/api/values");
201+
//
202+
// // try (MiseValidationResult validationResult = mise.validate(miseValidationInput)) { ... }
203+
// Object validationResult = miseClass.getMethod("validate", miseValidationInputClass)
204+
// .invoke(mise, miseValidationInput);
205+
//
206+
// // mise.unassignLogMessageCallback();
207+
// miseClass.getMethod("unassignLogMessageCallback")
208+
// .invoke(mise);
209+
//
210+
// Class<?> miseValidationResultClass = Class.forName("com.microsoft.identity.service.essentials.MiseValidationResult");
211+
// int statusCode = (int) miseValidationResultClass.getMethod("getHttpResponseStatusCode").invoke(validationResult);
212+
// LOGGER.info("Status code " + statusCode);
213+
//
214+
// String errorDescription = (String) miseValidationResultClass.getMethod("getErrorDescription").invoke(validationResult);
215+
// if (errorDescription != null) {
216+
// LOGGER.error("Error message " + errorDescription);
217+
// }
218+
// // Close validationResult if AutoCloseable
219+
// if (validationResult instanceof AutoCloseable) {
220+
// ((AutoCloseable) validationResult).close();
221+
// }
222+
// if (statusCode != 200) {
223+
// LOGGER.error("MISE token validation failed with status code: " + statusCode);
224+
// return false;
225+
// }
226+
// LOGGER.info("MISE token validation passed");
227+
// } catch (Exception e) {
228+
// e.printStackTrace();
229+
// return false;
230+
// }
231+
// return true;
232+
// }
233+
206234
private PublicKey getPublicKey(JWSObject jwsObject, JWKSet jwkSet) throws JOSEException {
207235
JWSAlgorithm algorithm = jwsObject.getHeader().getAlgorithm();
208236
if (!algorithm.equals(JWSAlgorithm.RS256)) {

center/src/main/resources/application-release.yml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,7 @@ spring:
1313
audience: ${MICROSOFT_PROVIDER_AUDIENCE}
1414
instance-uri: ${MICROSOFT_PROVIDER_INSTANCE_URI}
1515
mise-enabled: ${MICROSOFT_MISE_ENABLED:false}
16+
mise-endpoint: ${MICROSOFT_MISE_ENDPOINT}
1617
registration:
1718
azure-client:
1819
provider: azure-ad

0 commit comments

Comments
 (0)