package com.wouterbreukink.onedrive.client.authoriser; import com.google.api.client.http.GenericUrl; import com.google.api.client.http.HttpRequest; import com.google.api.client.http.HttpResponse; import com.google.api.client.http.HttpTransport; import com.google.api.client.http.apache.ApacheHttpTransport; import com.google.api.client.json.JsonFactory; import com.google.api.client.json.JsonObjectParser; import com.google.api.client.json.gson.GsonFactory; import com.google.api.client.util.Key; import com.google.api.client.util.Preconditions; import com.wouterbreukink.onedrive.client.OneDriveAPIException; import com.wouterbreukink.onedrive.client.resources.Authorisation; import org.apache.logging.log4j.LogManager; import org.apache.logging.log4j.Logger; import java.io.IOException; import java.nio.charset.Charset; import java.nio.file.Files; import java.nio.file.Path; import java.util.Arrays; import java.util.Date; import java.util.regex.Matcher; import java.util.regex.Pattern; class OneDriveAuthorisationProvider implements AuthorisationProvider { static final HttpTransport HTTP_TRANSPORT = new ApacheHttpTransport(); static final JsonFactory JSON_FACTORY = new GsonFactory(); private static final Logger log = LogManager.getLogger(OneDriveAuthorisationProvider.class.getName()); private static final String clientSecret = "to8fZAGMvD7Jr-NSdY1eVm4V7eaAtV5B"; private static final String clientId = "000000004015B68A"; private Path keyFile; private Authorisation authorisation; private Date lastFetched; public OneDriveAuthorisationProvider(Path keyFile) throws IOException { this.keyFile = Preconditions.checkNotNull(keyFile); if (!Files.exists(keyFile) || !Files.isRegularFile(keyFile)) { throw new OneDriveAPIException(401, String.format("Specified key file '%s' cannot be found.", keyFile)); } String[] keyFileContents = readToken(); switch (keyFileContents.length) { case 0: throw new OneDriveAPIException(401, String.format("Key file '%s' is empty.", keyFile)); case 1: String authCode = keyFileContents[0]; // If the user has pasted the entire URL then parse it Pattern url = Pattern.compile("https://login.live.com/oauth20_desktop.srf.*code=(.*)&.*"); Matcher m = url.matcher(authCode); if (m.matches()) { authCode = m.group(1); } getTokenFromCode(authCode); break; case 2: if (keyFileContents[0].equals(clientId)) { getTokenFromRefreshToken(keyFileContents[1]); } else { throw new OneDriveAPIException(401, "Key file does not match this application version."); } break; default: throw new OneDriveAPIException(401, "Expected key file with code and/or refresh token"); } } public static void printAuthInstructions() { String authString = String.format("%s?client_id=%s&response_type=code&scope=wl.signin%%20wl.offline_access%%20onedrive.readwrite&client_secret=%s&redirect_uri=%s", "https://login.live.com/oauth20_authorize.srf", clientId, clientSecret, "https://login.live.com/oauth20_desktop.srf"); log.info("To authorise this application ou must generate an authorisation token"); log.info("Open the following in a browser, sign on, wait until you are redirected to a blank page and then store the url in the address bar in your key file."); log.info("Authorisation URL: " + authString); } @Override public String getAccessToken() throws IOException { if (authorisation != null) { // Refresh if we know it is needed if (lastFetched.after(new Date(lastFetched.getTime() + authorisation.getExpiresIn() * 1000))) { log.info("Authorisation token has expired - refreshing"); getTokenFromRefreshToken(authorisation.getRefreshToken()); saveToken(); } return authorisation.getAccessToken(); } else { throw new IllegalStateException("Authoriser has not been initialised"); } } public void refresh() throws IOException { getTokenFromRefreshToken(authorisation.getRefreshToken()); saveToken(); } private void getTokenFromCode(final String code) throws IOException { log.debug("Fetching authorisation token using authorisation code"); HttpRequest request = HTTP_TRANSPORT.createRequestFactory().buildGetRequest(new GenericUrl("https://login.live.com/oauth20_token.srf") { @Key("client_id") private String id = clientId; @Key("client_secret") private String secret = clientSecret; @Key("code") private String authCode = code; @Key("grant_type") private String grantType = "authorization_code"; @Key("redirect_uri") private String redirect = "https://login.live.com/oauth20_desktop.srf"; }); request.setParser(new JsonObjectParser(JSON_FACTORY)); processResponse(request.execute()); } private void getTokenFromRefreshToken(final String refreshToken) throws IOException { log.debug("Fetching authorisation token using refresh token"); HttpRequest request = HTTP_TRANSPORT.createRequestFactory().buildGetRequest(new GenericUrl("https://login.live.com/oauth20_token.srf") { @Key("client_id") private String id = clientId; @Key("client_secret") private String secret = clientSecret; @Key("refresh_token") private String token = refreshToken; @Key("grant_type") private String grantType = "refresh_token"; @Key("redirect_uri") private String redirect = "https://login.live.com/oauth20_desktop.srf"; }); request.setParser(new JsonObjectParser(JSON_FACTORY)); processResponse(request.execute()); } private void processResponse(HttpResponse response) throws IOException { authorisation = response.parseAs(Authorisation.class); // Check for failures if (response.getStatusCode() != 200 || authorisation.getError() != null) { throw new OneDriveAPIException(response.getStatusCode(), String.format("Error code %d - %s (%s)", response.getStatusCode(), authorisation.getError(), authorisation.getErrorDescription())); } log.info("Fetched new authorisation token and refresh token for user " + authorisation.getUserId()); saveToken(); lastFetched = new Date(); } private String[] readToken() { try { return Files.readAllLines(keyFile, Charset.defaultCharset()).toArray(new String[1]); } catch (IOException e) { log.error("Unable to read key file", e); } return new String[0]; } private void saveToken() throws OneDriveAPIException { try { String[] content = new String[]{clientId, authorisation.getRefreshToken()}; Files.write(keyFile, Arrays.asList(content), Charset.defaultCharset()); } catch (IOException e) { log.error("Unable to write to key file ", e); } } }