package com.sequenceiq.cloudbreak.cloud.azure.task.interactivelogin; import static com.sequenceiq.cloudbreak.cloud.azure.AzureInteractiveLogin.XPLAT_CLI_CLIENT_ID; import java.io.IOException; import javax.inject.Inject; import javax.ws.rs.client.Client; import javax.ws.rs.client.ClientBuilder; import javax.ws.rs.client.Entity; import javax.ws.rs.client.Invocation; import javax.ws.rs.client.WebTarget; import javax.ws.rs.core.Form; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.Response; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import org.springframework.context.annotation.Scope; import org.springframework.stereotype.Component; import com.fasterxml.jackson.databind.ObjectMapper; import com.sequenceiq.cloudbreak.cloud.azure.AzureInteractiveLogin; import com.sequenceiq.cloudbreak.cloud.azure.context.AzureInteractiveLoginStatusCheckerContext; import com.sequenceiq.cloudbreak.cloud.azure.view.AzureCredentialView; import com.sequenceiq.cloudbreak.cloud.context.AuthenticatedContext; import com.sequenceiq.cloudbreak.cloud.context.CloudContext; import com.sequenceiq.cloudbreak.cloud.model.ExtendedCloudCredential; import com.sequenceiq.cloudbreak.cloud.task.PollBooleanStateTask; import reactor.bus.EventBus; /** * Created by perdos on 9/22/16. */ @Component(AzureInteractiveLoginStatusCheckerTask.NAME) @Scope(value = "prototype") public class AzureInteractiveLoginStatusCheckerTask extends PollBooleanStateTask { public static final String NAME = "armInteractiveLoginStatusCheckerTask"; public static final String GRAPH_WINDOWS = "https://graph.windows.net/"; public static final String GRAPH_API_VERSION = "1.6"; public static final String AZURE_MANAGEMENT = "https://management.azure.com/"; private static final String LOGIN_MICROSOFTONLINE = "https://login.microsoftonline.com/"; private static final String LOGIN_MICROSOFTONLINE_OAUTH2 = LOGIN_MICROSOFTONLINE + "common/oauth2"; private static final Logger LOGGER = LoggerFactory.getLogger(AzureInteractiveLoginStatusCheckerTask.class); private static final String PASSWORD = "cloudbreak"; private final AzureInteractiveLoginStatusCheckerContext armInteractiveLoginStatusCheckerContext; private Client client; @Inject private ApplicationCreator applicationCreator; @Inject private AzureRoleManager azureRoleManager; @Inject private PrincipalCreator principalCreator; @Inject private EventBus eventBus; public AzureInteractiveLoginStatusCheckerTask(CloudContext cloudContext, AzureInteractiveLoginStatusCheckerContext armInteractiveLoginStatusCheckerContext) { super(new AuthenticatedContext(cloudContext, armInteractiveLoginStatusCheckerContext.getExtendedCloudCredential()), false); this.armInteractiveLoginStatusCheckerContext = armInteractiveLoginStatusCheckerContext; } @Override public boolean cancelled() { return armInteractiveLoginStatusCheckerContext.isCancelled(); } @Override public Boolean call() { Response response = createPollingRequest(); if (response.getStatusInfo().getFamily() == Response.Status.Family.SUCCESSFUL) { String tokenResponseString = response.readEntity(String.class); try { String refreshToken = new ObjectMapper().readTree(tokenResponseString).get("refresh_token").asText(); LOGGER.info("Access token received"); ExtendedCloudCredential extendedCloudCredential = armInteractiveLoginStatusCheckerContext.getExtendedCloudCredential(); AzureCredentialView armCredentialView = new AzureCredentialView(extendedCloudCredential); try { String graphApiAccessToken = createResourceToken(refreshToken, armCredentialView.getTenantId(), GRAPH_WINDOWS); String managementApiToken = createResourceToken(refreshToken, armCredentialView.getTenantId(), AZURE_MANAGEMENT); String appId = applicationCreator.createApplication(graphApiAccessToken, armCredentialView.getTenantId()); sendStatusMessage(extendedCloudCredential, "Cloudbreak application created"); String principalObjectId = principalCreator.createServicePrincipal(graphApiAccessToken, appId, armCredentialView.getTenantId()); sendStatusMessage(extendedCloudCredential, "Principal created for application"); azureRoleManager.assignRole(managementApiToken, armCredentialView.getSubscriptionId(), principalObjectId); sendStatusMessage(extendedCloudCredential, "Role assigned for principal"); extendedCloudCredential.putParameter("accessKey", appId); extendedCloudCredential.putParameter("secretKey", PASSWORD); armInteractiveLoginStatusCheckerContext.getCredentialNotifier().createCredential(getAuthenticatedContext().getCloudContext(), extendedCloudCredential); } catch (InteractiveLoginException e) { LOGGER.error("Interactive login failed: ", e.getMessage()); sendErrorStatusMessage(extendedCloudCredential, e.getMessage()); } } catch (IOException e) { throw new IllegalStateException(e); } return true; } else { LOGGER.info("Polling request failed this time, status code {}, response: {}", response.getStatus(), response.readEntity(String.class)); return false; } } private void sendStatusMessage(ExtendedCloudCredential extendedCloudCredential, String message) { armInteractiveLoginStatusCheckerContext.getCredentialNotifier().sendStatusMessage(getAuthenticatedContext().getCloudContext(), extendedCloudCredential, false, message); } private void sendErrorStatusMessage(ExtendedCloudCredential extendedCloudCredential, String message) { armInteractiveLoginStatusCheckerContext.getCredentialNotifier().sendStatusMessage(getAuthenticatedContext().getCloudContext(), extendedCloudCredential, true, message); } private Response createPollingRequest() { Form pollingForm = createPollingForm(); WebTarget resource = ClientBuilder.newClient().target(LOGIN_MICROSOFTONLINE_OAUTH2); Invocation.Builder request = resource.path("token").queryParam("api-version", "1.0").request(); request.accept(MediaType.APPLICATION_JSON); return request.post(Entity.entity(pollingForm, MediaType.APPLICATION_FORM_URLENCODED_TYPE)); } private String createResourceToken(String refreshToken, String tenantId, String resource) throws InteractiveLoginException { Form resourceTokenForm = createResourceTokenForm(refreshToken, resource); WebTarget webTarget = ClientBuilder.newClient().target(LOGIN_MICROSOFTONLINE); Invocation.Builder request = webTarget.path(tenantId + "/oauth2/token").queryParam("api-version", "1.0").request(); request.accept(MediaType.APPLICATION_JSON); Response response = request.post(Entity.entity(resourceTokenForm, MediaType.APPLICATION_FORM_URLENCODED_TYPE)); if (response.getStatusInfo().getFamily() != Response.Status.Family.SUCCESSFUL) { throw new InteractiveLoginException("Obtain access token for " + resource + " failed " + "with tenant ID: " + tenantId + ", status code " + response.getStatus() + ", error message: " + response.readEntity(String.class)); } String responseString = response.readEntity(String.class); try { return new ObjectMapper().readTree(responseString).get("access_token").asText(); } catch (IOException e) { throw new IllegalStateException(e); } } private Form createPollingForm() { Form pollingForm = new Form(); pollingForm.param("grant_type", "device_code"); pollingForm.param("client_id", XPLAT_CLI_CLIENT_ID); pollingForm.param("resource", AzureInteractiveLogin.MANAGEMENT_CORE_WINDOWS); pollingForm.param("code", armInteractiveLoginStatusCheckerContext.getDeviceCode()); return pollingForm; } private Form createResourceTokenForm(String refreshToken, String resource) { Form graphApiTokenForm = new Form(); graphApiTokenForm.param("grant_type", "refresh_token"); graphApiTokenForm.param("client_id", XPLAT_CLI_CLIENT_ID); graphApiTokenForm.param("resource", resource); graphApiTokenForm.param("refresh_token", refreshToken); return graphApiTokenForm; } }