-
Notifications
You must be signed in to change notification settings - Fork 148
Add initial code for MSI #661
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Merged
Merged
Changes from all commits
Commits
Show all changes
10 commits
Select commit
Hold shift + click to select a range
60bbf7c
Add initial code for MSI
neha-bhargava 6688149
Update the MsalRequest object flow
neha-bhargava 9992111
Merge branch 'dev' of https://github.com/AzureAD/microsoft-authentica…
neha-bhargava 7d3f2e0
Update successful test and refactor request flow
neha-bhargava eb36681
Add more tests
neha-bhargava d7364ba
Minor changes
neha-bhargava 68a5180
Address comments
neha-bhargava 91ea7ce
Fix test with correct exception
neha-bhargava 9e3dcb3
Address comments
neha-bhargava 2a264bb
Address comments
neha-bhargava File filter
Filter by extension
Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
There are no files selected for viewing
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
139 changes: 139 additions & 0 deletions
139
msal4j-sdk/src/main/java/com/microsoft/aad/msal4j/AbstractManagedIdentitySource.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,139 @@ | ||
// Copyright (c) Microsoft Corporation. All rights reserved. | ||
// Licensed under the MIT License. | ||
|
||
package com.microsoft.aad.msal4j; | ||
|
||
import com.nimbusds.oauth2.sdk.ParseException; | ||
import com.nimbusds.oauth2.sdk.SerializeException; | ||
import com.nimbusds.oauth2.sdk.http.HTTPRequest; | ||
import com.nimbusds.oauth2.sdk.http.HTTPResponse; | ||
import lombok.Getter; | ||
import lombok.Setter; | ||
import org.slf4j.Logger; | ||
import org.slf4j.LoggerFactory; | ||
|
||
import java.beans.Encoder; | ||
import java.io.IOException; | ||
import java.net.HttpURLConnection; | ||
import java.net.MalformedURLException; | ||
import java.net.SocketException; | ||
import java.net.URISyntaxException; | ||
|
||
//base class for all sources that support managed identity | ||
abstract class AbstractManagedIdentitySource { | ||
|
||
protected static final String TIMEOUT_ERROR = "[Managed Identity] Authentication unavailable. The request to the managed identity endpoint timed out."; | ||
bgavrilMS marked this conversation as resolved.
Show resolved
Hide resolved
|
||
private static final Logger LOG = LoggerFactory.getLogger(AbstractManagedIdentitySource.class); | ||
private static final String MANAGED_IDENTITY_NO_RESPONSE_RECEIVED = "[Managed Identity] Authentication unavailable. No response received from the managed identity endpoint."; | ||
|
||
protected final ManagedIdentityRequest managedIdentityRequest; | ||
private ServiceBundle serviceBundle; | ||
private ManagedIdentitySourceType managedIdentitySourceType; | ||
|
||
@Getter | ||
@Setter | ||
private boolean isUserAssignedManagedIdentity; | ||
@Getter | ||
@Setter | ||
private String managedIdentityUserAssignedClientId; | ||
@Getter | ||
@Setter | ||
private String managedIdentityUserAssignedResourceId; | ||
|
||
public AbstractManagedIdentitySource(MsalRequest msalRequest, ServiceBundle serviceBundle, | ||
ManagedIdentitySourceType sourceType) { | ||
this.managedIdentityRequest = (ManagedIdentityRequest) msalRequest; | ||
this.managedIdentitySourceType = sourceType; | ||
this.serviceBundle = serviceBundle; | ||
} | ||
|
||
public ManagedIdentityResponse getManagedIdentityResponse( | ||
ManagedIdentityParameters parameters) { | ||
|
||
createManagedIdentityRequest(parameters.resource); | ||
IHttpResponse response; | ||
|
||
try { | ||
HttpRequest httpRequest = new HttpRequest(HttpMethod.GET, managedIdentityRequest.computeURI().toString(), managedIdentityRequest.headers); | ||
response = HttpHelper.executeHttpRequest(httpRequest, managedIdentityRequest.requestContext(), serviceBundle); | ||
} catch (URISyntaxException e) { | ||
throw new RuntimeException(e); | ||
} catch (MsalClientException e) { | ||
if (e.getCause() instanceof SocketException) { | ||
throw new MsalManagedIdentityException(MsalError.MANAGED_IDENTITY_UNREACHABLE_NETWORK, e.getMessage(), managedIdentitySourceType); | ||
} | ||
|
||
throw e; | ||
} | ||
|
||
return handleResponse(parameters, response); | ||
} | ||
|
||
public ManagedIdentityResponse handleResponse( | ||
ManagedIdentityParameters parameters, | ||
IHttpResponse response) { | ||
|
||
String message; | ||
|
||
try { | ||
if (response.statusCode() == HttpURLConnection.HTTP_OK) { | ||
neha-bhargava marked this conversation as resolved.
Show resolved
Hide resolved
|
||
LOG.info("[Managed Identity] Successful response received."); | ||
return getSuccessfulResponse(response); | ||
} else { | ||
message = getMessageFromErrorResponse(response); | ||
LOG.error( | ||
String.format("[Managed Identity] request failed, HttpStatusCode: %s Error message: %s", | ||
response.statusCode(), message)); | ||
throw new MsalManagedIdentityException(AuthenticationErrorCode.MANAGED_IDENTITY_REQUEST_FAILED, message, managedIdentitySourceType); | ||
} | ||
} catch (Exception e) { | ||
if (!(e instanceof MsalServiceException)) { | ||
LOG.error( | ||
String.format("[Managed Identity] Exception: %s Http status code: %s", e.getMessage(), | ||
response != null ? response.statusCode() : "")); | ||
message = MsalErrorMessage.MANAGED_IDENTITY_UNEXPECTED_RESPONSE; | ||
} else { | ||
throw e; | ||
} | ||
throw new MsalManagedIdentityException(AuthenticationErrorCode.MANAGED_IDENTITY_REQUEST_FAILED, message, managedIdentitySourceType); | ||
} | ||
} | ||
|
||
public abstract void createManagedIdentityRequest(String resource); | ||
|
||
protected ManagedIdentityResponse getSuccessfulResponse(IHttpResponse response) { | ||
|
||
ManagedIdentityResponse managedIdentityResponse = JsonHelper | ||
.convertJsonToObject(response.body(), ManagedIdentityResponse.class); | ||
|
||
if (managedIdentityResponse == null || managedIdentityResponse.getAccessToken() == null | ||
|| managedIdentityResponse.getAccessToken().isEmpty() || managedIdentityResponse.getExpiresOn() == null | ||
|| managedIdentityResponse.getExpiresOn().isEmpty()) { | ||
LOG.error("[Managed Identity] Response is either null or insufficient for authentication."); | ||
neha-bhargava marked this conversation as resolved.
Show resolved
Hide resolved
|
||
throw new MsalManagedIdentityException(MsalError.MANAGED_IDENTITY_REQUEST_FAILED, MsalErrorMessage.MANAGED_IDENTITY_UNEXPECTED_RESPONSE, managedIdentitySourceType); | ||
} | ||
|
||
return managedIdentityResponse; | ||
} | ||
|
||
protected String getMessageFromErrorResponse(IHttpResponse response) { | ||
ManagedIdentityErrorResponse managedIdentityErrorResponse = | ||
JsonHelper.convertJsonToObject(response.body(), ManagedIdentityErrorResponse.class); | ||
|
||
if (managedIdentityErrorResponse == null) { | ||
return MANAGED_IDENTITY_NO_RESPONSE_RECEIVED; | ||
} | ||
|
||
if (managedIdentityErrorResponse.getMessage() != null && !managedIdentityErrorResponse.getMessage().isEmpty()) { | ||
return String.format("[Managed Identity] Error Message: %s Managed Identity Correlation ID: %s Use this Correlation ID for further investigation.", | ||
managedIdentityErrorResponse.getMessage(), managedIdentityErrorResponse.getCorrelationId()); | ||
} | ||
|
||
return String.format("[Managed Identity] Error Code: %s Error Message: %s", | ||
managedIdentityErrorResponse.getError(), managedIdentityErrorResponse.getErrorDescription()); | ||
} | ||
|
||
protected static IEnvironmentVariables getEnvironmentVariables(ManagedIdentityParameters parameters) { | ||
return parameters.environmentVariables == null ? new EnvironmentVariables() : parameters.environmentVariables; | ||
} | ||
} |
103 changes: 103 additions & 0 deletions
103
msal4j-sdk/src/main/java/com/microsoft/aad/msal4j/AcquireTokenByManagedIdentitySupplier.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,103 @@ | ||
// Copyright (c) Microsoft Corporation. All rights reserved. | ||
// Licensed under the MIT License. | ||
|
||
package com.microsoft.aad.msal4j; | ||
|
||
import org.slf4j.Logger; | ||
import org.slf4j.LoggerFactory; | ||
|
||
import java.util.HashSet; | ||
import java.util.Set; | ||
|
||
class AcquireTokenByManagedIdentitySupplier extends AuthenticationResultSupplier { | ||
|
||
private static final Logger LOG = LoggerFactory.getLogger(AcquireTokenByManagedIdentitySupplier.class); | ||
|
||
private ManagedIdentityParameters managedIdentityParameters; | ||
|
||
AcquireTokenByManagedIdentitySupplier(ManagedIdentityApplication managedIdentityApplication, MsalRequest msalRequest) { | ||
super(managedIdentityApplication, msalRequest); | ||
this.managedIdentityParameters = (ManagedIdentityParameters) msalRequest.requestContext().apiParameters(); | ||
} | ||
|
||
@Override | ||
AuthenticationResult execute() throws Exception { | ||
|
||
if (StringHelper.isNullOrBlank(managedIdentityParameters.resource)) { | ||
throw new MsalClientException( | ||
MsalError.RESOURCE_REQUIRED_MANAGED_IDENTITY, | ||
MsalErrorMessage.SCOPES_REQUIRED); | ||
} | ||
|
||
TokenRequestExecutor tokenRequestExecutor = new TokenRequestExecutor( | ||
clientApplication.authenticationAuthority, | ||
msalRequest, | ||
clientApplication.getServiceBundle() | ||
); | ||
|
||
if (!managedIdentityParameters.forceRefresh) { | ||
bgavrilMS marked this conversation as resolved.
Show resolved
Hide resolved
|
||
LOG.debug("ForceRefresh set to false. Attempting cache lookup"); | ||
|
||
try { | ||
Set<String> scopes = new HashSet<>(); | ||
scopes.add(this.managedIdentityParameters.resource); | ||
SilentParameters parameters = SilentParameters | ||
.builder(scopes) | ||
.build(); | ||
|
||
RequestContext context = new RequestContext( | ||
this.clientApplication, | ||
PublicApi.ACQUIRE_TOKEN_SILENTLY, | ||
parameters); | ||
|
||
SilentRequest silentRequest = new SilentRequest( | ||
parameters, | ||
this.clientApplication, | ||
context, | ||
null); | ||
|
||
AcquireTokenSilentSupplier supplier = new AcquireTokenSilentSupplier( | ||
this.clientApplication, | ||
silentRequest); | ||
|
||
return supplier.execute(); | ||
} catch (MsalClientException ex) { | ||
bgavrilMS marked this conversation as resolved.
Show resolved
Hide resolved
|
||
if (ex.errorCode().equals(AuthenticationErrorCode.CACHE_MISS)) { | ||
LOG.debug(String.format("Cache lookup failed: %s", ex.getMessage())); | ||
return fetchNewAccessTokenAndSaveToCache(tokenRequestExecutor, clientApplication.authenticationAuthority.host); | ||
} else { | ||
LOG.error("Error occurred while cache lookup. " + ex.getMessage()); | ||
throw ex; | ||
} | ||
} | ||
} | ||
|
||
LOG.info("Skipped looking for an Access Token in the cache because forceRefresh or Claims were set. "); | ||
return fetchNewAccessTokenAndSaveToCache(tokenRequestExecutor, clientApplication.authenticationAuthority.host); | ||
} | ||
|
||
private AuthenticationResult fetchNewAccessTokenAndSaveToCache(TokenRequestExecutor tokenRequestExecutor, String host) throws Exception { | ||
|
||
ManagedIdentityClient managedIdentityClient = new ManagedIdentityClient(msalRequest, tokenRequestExecutor.getServiceBundle()); | ||
|
||
ManagedIdentityResponse managedIdentityResponse = managedIdentityClient | ||
.getManagedIdentityResponse(managedIdentityParameters); | ||
|
||
AuthenticationResult authenticationResult = createFromManagedIdentityResponse(managedIdentityResponse); | ||
clientApplication.tokenCache.saveTokens(tokenRequestExecutor, authenticationResult, clientApplication.authenticationAuthority.host); | ||
return authenticationResult; | ||
} | ||
|
||
private AuthenticationResult createFromManagedIdentityResponse(ManagedIdentityResponse managedIdentityResponse) { | ||
long expiresOn = Long.valueOf(managedIdentityResponse.expiresOn); | ||
long refreshOn = expiresOn > 2 * 3600 ? (expiresOn / 2) : 0L; | ||
neha-bhargava marked this conversation as resolved.
Show resolved
Hide resolved
|
||
|
||
return AuthenticationResult.builder() | ||
.accessToken(managedIdentityResponse.getAccessToken()) | ||
.scopes(managedIdentityParameters.getResource()) | ||
.expiresOn(expiresOn) | ||
.extExpiresOn(0) | ||
.refreshOn(refreshOn) | ||
.build(); | ||
} | ||
} |
98 changes: 98 additions & 0 deletions
98
msal4j-sdk/src/main/java/com/microsoft/aad/msal4j/AppServiceManagedIdentitySource.java
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,98 @@ | ||
// Copyright (c) Microsoft Corporation. All rights reserved. | ||
// Licensed under the MIT License. | ||
|
||
package com.microsoft.aad.msal4j; | ||
|
||
import org.slf4j.Logger; | ||
import org.slf4j.LoggerFactory; | ||
|
||
import java.net.URI; | ||
import java.net.URISyntaxException; | ||
import java.util.HashMap; | ||
import java.util.Map; | ||
|
||
class AppServiceManagedIdentitySource extends AbstractManagedIdentitySource{ | ||
|
||
private static final Logger LOG = LoggerFactory.getLogger(AppServiceManagedIdentitySource.class); | ||
|
||
// MSI Constants. Docs for MSI are available here https://docs.microsoft.com/azure/app-service/overview-managed-identity | ||
private static final String APP_SERVICE_MSI_API_VERSION = "2019-08-01"; | ||
private static final String SECRET_HEADER_NAME = "X-IDENTITY-HEADER"; | ||
private static URI endpointUri; | ||
|
||
private URI endpoint; | ||
private String secret; | ||
|
||
@Override | ||
public void createManagedIdentityRequest(String resource) { | ||
managedIdentityRequest.baseEndpoint = endpoint; | ||
managedIdentityRequest.method = HttpMethod.GET; | ||
|
||
Map<String, String> headers = new HashMap<>(); | ||
headers.put(SECRET_HEADER_NAME, secret); | ||
managedIdentityRequest.headers = headers; | ||
|
||
Map<String, String> queryParameters = new HashMap<>(); | ||
queryParameters.put("api-version", APP_SERVICE_MSI_API_VERSION ); | ||
queryParameters.put("resource", resource); | ||
|
||
if (!StringHelper.isNullOrBlank(getManagedIdentityUserAssignedClientId())) | ||
{ | ||
LOG.info("[Managed Identity] Adding user assigned client id to the request."); | ||
queryParameters.put(Constants.MANAGED_IDENTITY_CLIENT_ID, getManagedIdentityUserAssignedClientId()); | ||
} | ||
|
||
if (!StringHelper.isNullOrBlank(getManagedIdentityUserAssignedResourceId())) | ||
{ | ||
LOG.info("[Managed Identity] Adding user assigned resource id to the request."); | ||
queryParameters.put(Constants.MANAGED_IDENTITY_RESOURCE_ID, getManagedIdentityUserAssignedResourceId()); | ||
} | ||
|
||
managedIdentityRequest.queryParameters = queryParameters; | ||
} | ||
|
||
private AppServiceManagedIdentitySource(MsalRequest msalRequest, ServiceBundle serviceBundle, URI endpoint, String secret) | ||
{ | ||
super(msalRequest, serviceBundle, ManagedIdentitySourceType.AppService); | ||
this.endpoint = endpoint; | ||
this.secret = secret; | ||
} | ||
|
||
protected static AbstractManagedIdentitySource create(MsalRequest msalRequest, ServiceBundle serviceBundle) { | ||
|
||
IEnvironmentVariables environmentVariables = getEnvironmentVariables((ManagedIdentityParameters) msalRequest.requestContext().apiParameters()); | ||
String msiSecret = environmentVariables.getEnvironmentVariable(Constants.IDENTITY_HEADER); | ||
String msiEndpoint = environmentVariables.getEnvironmentVariable(Constants.IDENTITY_ENDPOINT); | ||
|
||
return validateEnvironmentVariables(msiEndpoint, msiSecret) | ||
? new AppServiceManagedIdentitySource(msalRequest, serviceBundle, endpointUri, msiSecret) | ||
: null; | ||
} | ||
|
||
private static boolean validateEnvironmentVariables(String msiEndpoint, String secret) | ||
{ | ||
endpointUri = null; | ||
|
||
// if BOTH the env vars endpoint and secret values are null, this MSI provider is unavailable. | ||
if (StringHelper.isNullOrBlank(msiEndpoint) || StringHelper.isNullOrBlank(secret)) | ||
{ | ||
LOG.info("[Managed Identity] App service managed identity is unavailable."); | ||
return false; | ||
} | ||
|
||
try | ||
{ | ||
endpointUri = new URI(msiEndpoint); | ||
} | ||
catch (URISyntaxException ex) | ||
{ | ||
throw new MsalManagedIdentityException(MsalError.INVALID_MANAGED_IDENTITY_ENDPOINT, String.format( | ||
MsalErrorMessage.MANAGED_IDENTITY_ENDPOINT_INVALID_URI_ERROR, "IDENTITY_ENDPOINT", msiEndpoint, "App Service"), | ||
ManagedIdentitySourceType.AppService); | ||
} | ||
|
||
LOG.info("[Managed Identity] Environment variables validation passed for app service managed identity. Endpoint URI: {endpointUri}. Creating App Service managed identity."); | ||
return true; | ||
} | ||
|
||
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Oops, something went wrong.
Add this suggestion to a batch that can be applied as a single commit.
This suggestion is invalid because no changes were made to the code.
Suggestions cannot be applied while the pull request is closed.
Suggestions cannot be applied while viewing a subset of changes.
Only one suggestion per line can be applied in a batch.
Add this suggestion to a batch that can be applied as a single commit.
Applying suggestions on deleted lines is not supported.
You must change the existing code in this line in order to create a valid suggestion.
Outdated suggestions cannot be applied.
This suggestion has been applied or marked resolved.
Suggestions cannot be applied from pending reviews.
Suggestions cannot be applied on multi-line comments.
Suggestions cannot be applied while the pull request is queued to merge.
Suggestion cannot be applied right now. Please check back later.
Uh oh!
There was an error while loading. Please reload this page.