package io.loli.sc.api;
import io.loli.sc.config.Config;
import java.awt.Desktop;
import java.awt.HeadlessException;
import java.io.BufferedInputStream;
import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.io.StringReader;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import com.google.api.client.auth.oauth2.Credential;
import com.google.api.client.googleapis.auth.oauth2.GoogleAuthorizationCodeFlow;
import com.google.api.client.googleapis.auth.oauth2.GoogleAuthorizationCodeRequestUrl;
import com.google.api.client.googleapis.auth.oauth2.GoogleClientSecrets;
import com.google.api.client.googleapis.auth.oauth2.GoogleCredential;
import com.google.api.client.googleapis.auth.oauth2.GoogleTokenResponse;
import com.google.api.client.googleapis.media.MediaHttpUploader;
import com.google.api.client.http.HttpTransport;
import com.google.api.client.http.InputStreamContent;
import com.google.api.client.http.javanet.NetHttpTransport;
import com.google.api.client.json.JsonFactory;
import com.google.api.client.json.jackson2.JacksonFactory;
import com.google.api.services.drive.Drive;
import com.google.api.services.drive.Drive.Files;
import com.google.api.services.drive.model.FileList;
import com.google.api.services.drive.model.ParentReference;
import com.google.api.services.drive.model.Permission;
import com.google.api.services.oauth2.Oauth2;
import com.google.api.services.oauth2.model.Userinfo;
public class GDriveAPI extends APITools implements API {
private final static String CLIENT_ID = "843116795212.apps.googleusercontent.com";
private final static String CLIENT_SECRET = "7dtggnvbXOVsV0GV0N3FieXp";
private final static String REDIRECT_URL = "urn:ietf:wg:oauth:2.0:oob";
private final static String AUTH = "https://accounts.google.com/o/oauth2/auth?response_type=code&client_id="
+ CLIENT_ID
+ "&redirect_uri="
+ REDIRECT_URL
+ "&scope=https://www.googleapis.com/auth/drive.file%20https://www.googleapis.com/auth/userinfo.email%20https://www.googleapis.com/auth/userinfo.profile&approval_prompt=auto";
private Config config;
@Override
public String upload(File fileToUpload) throws UploadException {
com.google.api.services.drive.model.File f = null;
try {
Credential cre = GDriveUpload.tokenToCre(config.getGdriveConfig()
.getAccessToken(), config.getGdriveConfig()
.getRefreshToken());
cre.refreshToken();
Drive drive = GDriveUpload.getDrive(cre);
String parent_id = GDriveUpload.getFolderId(drive);
if (parent_id == null) {
parent_id = GDriveUpload.createFolder("sc-java", cre);
}
f = GDriveUpload.uploadFile(fileToUpload, cre, parent_id);
GDriveUpload.insertPermission(drive, f.getId(), "", "anyone",
"reader");
} catch (HeadlessException | IOException e) {
throw new UploadException(e);
}
try {
f.setWebViewLink(f.getWebContentLink().substring(0,
f.getWebContentLink().indexOf("&")));
} catch (Exception e) {
throw new UploadException(e);
}
return f.getWebViewLink();
}
public GDriveAPI() {
}
public GDriveAPI(Config config) {
this.config = config;
}
private String code;
@Override
public void auth() throws UploadException {
Desktop desktop = Desktop.getDesktop();
try {
desktop.browse(new URI(AUTH));
} catch (IOException | URISyntaxException e) {
throw new UploadException(e);
}
}
public AccessToken pinToToken(String pin) throws UploadException {
Credential c = null;
try {
c = GDriveAuth.exchangeCode(pin);
} catch (io.loli.sc.api.GDriveAPI.GDriveAuth.CodeExchangeException e) {
throw new UploadException(e);
}
AccessToken token = new AccessToken();
token.setAccess_token(c.getAccessToken());
token.setExpires_in(c.getExpiresInSeconds());
token.setRefresh_token(c.getRefreshToken());
return token;
}
public String getCode() {
return code;
}
public static class AccessToken {
private String access_token;
private long expires_in;
private String refresh_token;
public String getAccess_token() {
return access_token;
}
public void setExpires_in(long expires_in) {
this.expires_in = expires_in;
}
public void setAccess_token(String access_token) {
this.access_token = access_token;
}
public String getRefresh_token() {
return refresh_token;
}
public void setRefresh_token(String refresh_token) {
this.refresh_token = refresh_token;
}
public long getExpires_in() {
return expires_in;
}
}
public static class NewAccessToken {
private String Access_token;
private int Expires_in;
private String Token_type;
public String getAccess_token() {
return Access_token;
}
public void setAccess_token(String access_token) {
Access_token = access_token;
}
public int getExpires_in() {
return Expires_in;
}
public void setExpires_in(int expires_in) {
Expires_in = expires_in;
}
public String getToken_type() {
return Token_type;
}
public void setToken_type(String token_type) {
Token_type = token_type;
}
}
static class GDriveUpload {
private static final HttpTransport HTTP_TRANSPORT = new NetHttpTransport();
private static final JsonFactory JSON_FACTORY = new JacksonFactory();
private static Drive drive = null;
public static Drive getDrive(Credential credential) {
drive = new Drive.Builder(HTTP_TRANSPORT, JSON_FACTORY, credential)
.setApplicationName("Google-DriveSample/1.0").build();
return drive;
}
/**
* Insert a new permission.
*
* @param service Drive API service instance.
* @param fileId ID of the file to insert permission for.
* @param value User or group e-mail address, domain name or
* {@code null} "default" type.
* @param type The value "user", "group", "domain" or "default".
* @param role The value "owner", "writer" or "reader".
* @return The inserted permission if successful, {@code null}
* otherwise.
* @throws UploadException
*/
public static Permission insertPermission(Drive service, String fileId,
String value, String type, String role) throws UploadException {
Permission newPermission = new Permission();
newPermission.setValue(value);
newPermission.setType(type);
newPermission.setRole(role);
try {
return service.permissions().insert(fileId, newPermission)
.execute();
} catch (IOException e) {
throw new UploadException(e);
}
}
public static com.google.api.services.drive.model.File uploadFile(
java.io.File file, Credential credential, String parent)
throws IOException {
com.google.api.services.drive.model.File fileMetadata = new com.google.api.services.drive.model.File();
fileMetadata.setParents(Arrays.asList(new ParentReference()
.setId(parent)));
fileMetadata.setTitle(file.getName());
InputStreamContent mediaContent = new InputStreamContent(
"image/png", new BufferedInputStream(new FileInputStream(
file)));
mediaContent.setLength(file.length());
Drive.Files.Insert insert = drive.files().insert(fileMetadata,
mediaContent);
MediaHttpUploader uploader = insert.getMediaHttpUploader();
uploader.setDirectUploadEnabled(true);
return insert.execute();
}
public static Credential tokenToCre(String accessToken,
String refreshToken) {
GoogleCredential credential = new GoogleCredential.Builder()
.setJsonFactory(JSON_FACTORY).setTransport(HTTP_TRANSPORT)
.setClientSecrets(CLIENT_ID, CLIENT_SECRET).build()
.setAccessToken(accessToken).setRefreshToken(refreshToken);
return credential;
}
public static String createFolder(String name, Credential credential) throws UploadException {
com.google.api.services.drive.model.File body = new com.google.api.services.drive.model.File();
body.setTitle(name);
body.setMimeType("application/vnd.google-apps.folder");
Drive drive = new Drive.Builder(HTTP_TRANSPORT, JSON_FACTORY,
credential).setApplicationName("Google-DriveSample/1.0")
.build();
com.google.api.services.drive.model.File result = null;
try {
result = drive.files().insert(body).execute();
} catch (IOException e) {
throw new UploadException(e);
}
return result.getId();
}
public static String getFolderId(Drive service) throws IOException {
List<com.google.api.services.drive.model.File> result = new ArrayList<com.google.api.services.drive.model.File>();
Files.List request = service.files().list();
String parent_id = null;
do {
try {
FileList files = request.execute();
result.addAll(files.getItems());
request.setPageToken(files.getNextPageToken());
} catch (IOException e) {
System.out.println("An error occurred: " + e);
request.setPageToken(null);
}
} while (request.getPageToken() != null
&& request.getPageToken().length() > 0);
for (com.google.api.services.drive.model.File file : result) {
if (file.getTitle().equals("sc-java")
&& !file.getLabels().getTrashed()) {
parent_id = file.getId();
break;
}
}
return parent_id;
}
public static Credential refreshToken(Credential cre)
throws UploadException {
try {
cre.refreshToken();
} catch (IOException e) {
throw new UploadException(e);
}
return cre;
}
}
static class GDriveAuth {
private static String s = "{\"web\": {\"client_id\": \"843116795212.apps.googleusercontent.com\",\"client_secret\": \"7dtggnvbXOVsV0GV0N3FieXp\",\"auth_uri\": \"https://accounts.google.com/o/oauth2/auth\",\"token_uri\": \"https://accounts.google.com/o/oauth2/token\"}}";
private static final String REDIRECT_URI = "urn:ietf:wg:oauth:2.0:oob";
private static final List<String> SCOPES = Arrays.asList(
"https://www.googleapis.com/auth/drive.file",
"https://www.googleapis.com/auth/userinfo.email",
"https://www.googleapis.com/auth/userinfo.profile");
private static GoogleAuthorizationCodeFlow flow = null;
/**
* Exception thrown when an error occurred while retrieving credentials.
*/
public static class GetCredentialsException extends Exception {
/**
*
*/
private static final long serialVersionUID = 1L;
protected String authorizationUrl;
/**
* Construct a GetCredentialsException.
*
* @param authorizationUrl The authorization URL to redirect the
* user to.
*/
public GetCredentialsException(String authorizationUrl) {
this.authorizationUrl = authorizationUrl;
}
/**
* Set the authorization URL.
*/
public void setAuthorizationUrl(String authorizationUrl) {
this.authorizationUrl = authorizationUrl;
}
/**
* @return the authorizationUrl
*/
public String getAuthorizationUrl() {
return authorizationUrl;
}
}
/**
* Exception thrown when a code exchange has failed.
*/
public static class CodeExchangeException extends
GetCredentialsException {
/**
*
*/
private static final long serialVersionUID = 1L;
/**
* Construct a CodeExchangeException.
*
* @param authorizationUrl The authorization URL to redirect the
* user to.
*/
public CodeExchangeException(String authorizationUrl) {
super(authorizationUrl);
}
}
/**
* Exception thrown when no refresh token has been found.
*/
public static class NoRefreshTokenException extends
GetCredentialsException {
/**
*
*/
private static final long serialVersionUID = 1L;
/**
* Construct a NoRefreshTokenException.
*
* @param authorizationUrl The authorization URL to redirect the
* user to.
*/
public NoRefreshTokenException(String authorizationUrl) {
super(authorizationUrl);
}
}
/**
* Exception thrown when no user ID could be retrieved.
*/
private static class NoUserIdException extends Exception {
/**
*
*/
private static final long serialVersionUID = 1L;
}
/**
* Retrieved stored credentials for the provided user ID.
*
* @param userId User's ID.
* @return Stored Credential if found, {@code null} otherwise.
*/
static Credential getStoredCredentials(String userId) {
// TODO: Implement this method to work with your database.
// Instantiate a
// new
// Credential instance with stored accessToken and refreshToken.
throw new UnsupportedOperationException();
}
/**
* Store OAuth 2.0 credentials in the application's database.
*
* @param userId User's ID.
* @param credentials The OAuth 2.0 credentials to store.
*/
static void storeCredentials(String userId, Credential credentials) {
// TODO: Implement this method to work with your database.
// Store the credentials.getAccessToken() and
// credentials.getRefreshToken()
// string values in your database.
throw new UnsupportedOperationException();
}
/**
* Build an authorization flow and store it as a static class attribute.
*
* @return GoogleAuthorizationCodeFlow instance.
* @throws IOException Unable to load client_secrets.json.
*/
static GoogleAuthorizationCodeFlow getFlow() throws IOException {
if (flow == null) {
HttpTransport httpTransport = new NetHttpTransport();
JacksonFactory jsonFactory = new JacksonFactory();
GoogleClientSecrets clientSecrets = GoogleClientSecrets.load(
jsonFactory, new BufferedReader(new StringReader(s)));
flow = new GoogleAuthorizationCodeFlow.Builder(httpTransport,
jsonFactory, clientSecrets, SCOPES)
.setAccessType("offline").setApprovalPrompt("force")
.build();
}
return flow;
}
/**
* Exchange an authorization code for OAuth 2.0 credentials.
*
* @param authorizationCode Authorization code to exchange for OAuth 2.0
* credentials.
* @return OAuth 2.0 credentials.
* @throws CodeExchangeException An error occurred.
*/
static Credential exchangeCode(String authorizationCode)
throws CodeExchangeException {
try {
GoogleAuthorizationCodeFlow flow = getFlow();
GoogleTokenResponse response = flow
.newTokenRequest(authorizationCode)
.setRedirectUri(REDIRECT_URI).execute();
return flow.createAndStoreCredential(response, null);
} catch (IOException e) {
System.err.println("An error occurred: " + e);
throw new CodeExchangeException(null);
}
}
/**
* Send a request to the UserInfo API to retrieve the user's
* information.
*
* @param credentials OAuth 2.0 credentials to authorize the request.
* @return User's information.
* @throws NoUserIdException An error occurred.
*/
static Userinfo getUserInfo(Credential credentials)
throws NoUserIdException {
Oauth2 userInfoService = new Oauth2.Builder(new NetHttpTransport(),
new JacksonFactory(), credentials).build();
Userinfo userInfo = null;
try {
userInfo = userInfoService.userinfo().get().execute();
} catch (IOException e) {
System.err.println("An error occurred: " + e);
}
if (userInfo != null && userInfo.getId() != null) {
return userInfo;
} else {
throw new NoUserIdException();
}
}
/**
* Retrieve the authorization URL.
*
* @param emailAddress User's e-mail address.
* @param state State for the authorization URL.
* @return Authorization URL to redirect the user to.
* @throws IOException Unable to load client_secrets.json.
*/
public static String getAuthorizationUrl(String emailAddress,
String state) throws IOException {
GoogleAuthorizationCodeRequestUrl urlBuilder = getFlow()
.newAuthorizationUrl().setRedirectUri(REDIRECT_URI)
.setState(state);
urlBuilder.set("user_id", emailAddress);
return urlBuilder.build();
}
/**
* Retrieve credentials using the provided authorization code.
*
* This function exchanges the authorization code for an access token
* and queries the UserInfo API to retrieve the user's e-mail address.
* If a refresh token has been retrieved along with an access token, it
* is stored in the application database using the user's e-mail address
* as key. If no refresh token has been retrieved, the function checks
* in the application database for one and returns it if found or throws
* a NoRefreshTokenException with the authorization URL to redirect the
* user to.
*
* @param authorizationCode Authorization code to use to retrieve an
* access token.
* @param state State to set to the authorization URL in case of error.
* @return OAuth 2.0 credentials instance containing an access and
* refresh token.
* @throws NoRefreshTokenException No refresh token could be retrieved
* from the available sources.
* @throws IOException Unable to load client_secrets.json.
*/
public static Credential getCredentials(String authorizationCode,
String state) throws CodeExchangeException,
NoRefreshTokenException, IOException {
String emailAddress = "";
try {
Credential credentials = exchangeCode(authorizationCode);
Userinfo userInfo = getUserInfo(credentials);
String userId = userInfo.getId();
emailAddress = userInfo.getEmail();
if (credentials.getRefreshToken() != null) {
storeCredentials(userId, credentials);
return credentials;
} else {
credentials = getStoredCredentials(userId);
if (credentials != null
&& credentials.getRefreshToken() != null) {
return credentials;
}
}
} catch (CodeExchangeException e) {
e.printStackTrace();
// Drive apps should try to retrieve the user and credentials
// for
// the current
// session.
// If none is available, redirect the user to the authorization
// URL.
e.setAuthorizationUrl(getAuthorizationUrl(emailAddress, state));
throw e;
} catch (NoUserIdException e) {
e.printStackTrace();
}
// No refresh token has been retrieved.
String authorizationUrl = getAuthorizationUrl(emailAddress, state);
throw new NoRefreshTokenException(authorizationUrl);
}
}
}