package com.sequenceiq.cloudbreak.cloud.azure; import java.io.IOException; import java.util.HashMap; import java.util.Map; import java.util.concurrent.Executor; import java.util.concurrent.Executors; import javax.annotation.PostConstruct; 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.Service; import com.fasterxml.jackson.databind.JsonNode; import com.fasterxml.jackson.databind.ObjectMapper; import com.sequenceiq.cloudbreak.cloud.azure.context.AzureInteractiveLoginStatusCheckerContext; import com.sequenceiq.cloudbreak.cloud.azure.task.AzurePollTaskFactory; import com.sequenceiq.cloudbreak.cloud.context.CloudContext; import com.sequenceiq.cloudbreak.cloud.credential.CredentialNotifier; import com.sequenceiq.cloudbreak.cloud.model.ExtendedCloudCredential; import com.sequenceiq.cloudbreak.cloud.scheduler.SyncPollingScheduler; import com.sequenceiq.cloudbreak.cloud.task.PollTask; /** * Created by perdos on 9/22/16. */ @Service @Scope(value = "singleton") public class AzureInteractiveLogin { public static final String XPLAT_CLI_CLIENT_ID = "04b07795-8ddb-461a-bbee-02f9e1bf7b46"; public static final String MANAGEMENT_CORE_WINDOWS = "https://management.core.windows.net/"; private static final Logger LOGGER = LoggerFactory.getLogger(AzureInteractiveLogin.class); private Executor executor; private AzureInteractiveLoginStatusCheckerContext azureInteractiveLoginStatusCheckerContext; @Inject private AzurePollTaskFactory azurePollTaskFactory; @Inject private SyncPollingScheduler<Boolean> syncPollingScheduler; @PostConstruct public void init() { executor = Executors.newSingleThreadExecutor(); } public Map<String, String> login(CloudContext cloudContext, ExtendedCloudCredential extendedCloudCredential, CredentialNotifier credentialNotifier) { Response deviceCodeResponse = getDeviceCode(); if (deviceCodeResponse.getStatusInfo().getFamily() == Response.Status.Family.SUCCESSFUL) { LOGGER.info("Successful device code response: " + deviceCodeResponse.getStatus()); String jsonString = deviceCodeResponse.readEntity(String.class); LOGGER.info("Device code json response: " + jsonString); try { JsonNode deviceCodeJsonNode = new ObjectMapper().readTree(jsonString); int pollInterval = deviceCodeJsonNode.get("interval").asInt(); int expiresIn = deviceCodeJsonNode.get("expires_in").asInt(); String deviceCode = deviceCodeJsonNode.get("device_code").asText(); createCheckerContextAndCancelPrevious(extendedCloudCredential, deviceCode, credentialNotifier); startAsyncPolling(cloudContext, pollInterval, expiresIn); return extractParameters(deviceCodeJsonNode); } catch (IOException e) { throw new IllegalStateException(e); } } else { LOGGER.error("interactive login error, status: " + deviceCodeResponse.getStatus()); throw new IllegalStateException("interactive login error"); } } private void startAsyncPolling(CloudContext cloudContext, int pollInterval, int expiresIn) { PollTask<Boolean> interactiveLoginStatusCheckerTask = azurePollTaskFactory.interactiveLoginStatusCheckerTask( cloudContext, azureInteractiveLoginStatusCheckerContext); executor.execute(() -> { try { syncPollingScheduler.schedule(interactiveLoginStatusCheckerTask, pollInterval, expiresIn / pollInterval, 1); } catch (Exception e) { LOGGER.error("Interactive login schedule failed", e); } }); } private void createCheckerContextAndCancelPrevious(ExtendedCloudCredential extendedCloudCredential, String deviceCode, CredentialNotifier credentialNotifier) { if (azureInteractiveLoginStatusCheckerContext != null) { azureInteractiveLoginStatusCheckerContext.cancel(); } azureInteractiveLoginStatusCheckerContext = new AzureInteractiveLoginStatusCheckerContext(deviceCode, extendedCloudCredential, credentialNotifier); } private Map<String, String> extractParameters(JsonNode deviceCodeJsonNode) { Map<String, String> parameters = new HashMap<>(); parameters.put("user_code", deviceCodeJsonNode.get("user_code").asText()); parameters.put("verification_url", deviceCodeJsonNode.get("verification_url").asText()); return parameters; } private Response getDeviceCode() { Client client = ClientBuilder.newClient(); WebTarget resource = client.target("https://login.microsoftonline.com/common/oauth2"); Form form = new Form(); form.param("client_id", XPLAT_CLI_CLIENT_ID); form.param("resource", MANAGEMENT_CORE_WINDOWS); form.param("mkt", "en-us"); Invocation.Builder request = resource.path("devicecode").queryParam("api-version", "1.0").request(); request.accept(MediaType.APPLICATION_JSON); return request.post(Entity.entity(form, MediaType.APPLICATION_FORM_URLENCODED_TYPE)); } }