diff --git a/google-api-client-appengine/src/main/java/com/google/api/client/googleapis/extensions/appengine/auth/oauth2/GoogleAppEngineOAuthApplicationContext.java b/google-api-client-appengine/src/main/java/com/google/api/client/googleapis/extensions/appengine/auth/oauth2/GoogleAppEngineOAuthApplicationContext.java new file mode 100644 index 00000000..4170ae0c --- /dev/null +++ b/google-api-client-appengine/src/main/java/com/google/api/client/googleapis/extensions/appengine/auth/oauth2/GoogleAppEngineOAuthApplicationContext.java @@ -0,0 +1,79 @@ +/* + * Copyright (c) 2013 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.api.client.googleapis.extensions.appengine.auth.oauth2; + +import com.google.api.client.auth.oauth2.AuthorizationCodeFlow; +import com.google.api.client.extensions.appengine.auth.oauth2.AppEngineOAuthContext; +import com.google.api.client.googleapis.auth.oauth2.GoogleAuthorizationCodeFlow; +import com.google.api.client.googleapis.auth.oauth2.GoogleClientSecrets; + +import java.io.IOException; +import java.io.InputStreamReader; +import java.util.Collection; +import java.util.concurrent.locks.ReentrantLock; + +/** + * Thread safe OAuth 2.0 authorization appengine application context. It extends + * {@link AppEngineOAuthContext} and add implementation for both + * {@link #getClientSecrets} and {@link #getFlow}. + * + * @author Nick Miceli + * @author Eyal Peled + * + * @since 1.18 + * + */ +public class GoogleAppEngineOAuthApplicationContext extends AppEngineOAuthContext { + + private AuthorizationCodeFlow flow; + private GoogleClientSecrets clientSecrets; + + private final String clientSecretsPath; + private final ReentrantLock lock = new ReentrantLock(); + + public GoogleAppEngineOAuthApplicationContext(String redirectUri, String clientSecretsPath, + Collection scopes, String applicationName) { + super(redirectUri, scopes, applicationName); + this.clientSecretsPath = clientSecretsPath; + } + + protected GoogleClientSecrets getClientSecrets() throws IOException { + lock.lock(); + try { + if (clientSecrets == null) { + clientSecrets = GoogleClientSecrets.load(getJsonFactory(), new InputStreamReader( + GoogleAppEngineOAuthApplicationContext.class.getResourceAsStream(clientSecretsPath))); + } + } finally { + lock.unlock(); + } + return clientSecrets; + } + + @Override // TODO(NOW): Lock / thread-safety + public AuthorizationCodeFlow getFlow() throws IOException { + lock.lock(); + try { + if (flow == null) { + flow = new GoogleAuthorizationCodeFlow.Builder(getTransport(), getJsonFactory(), + getClientSecrets(), getScopes()).setDataStoreFactory(getDataStoreFactory()) + .setAccessType("offline").build(); + } + } finally { + lock.unlock(); + } + return flow; + } +} diff --git a/google-api-client-appengine/src/main/java/com/google/api/client/googleapis/extensions/appengine/utils/ServiceFactory.java b/google-api-client-appengine/src/main/java/com/google/api/client/googleapis/extensions/appengine/utils/ServiceFactory.java new file mode 100644 index 00000000..cb77c4ec --- /dev/null +++ b/google-api-client-appengine/src/main/java/com/google/api/client/googleapis/extensions/appengine/utils/ServiceFactory.java @@ -0,0 +1,67 @@ +/* + * Copyright (c) 2013 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.api.client.googleapis.extensions.appengine.utils; + +import com.google.api.client.auth.oauth2.Credential; +import com.google.api.client.auth.oauth2.OAuthContext; +import com.google.api.client.googleapis.services.json.AbstractGoogleJsonClient; +import com.google.api.client.googleapis.services.json.AbstractGoogleJsonClient.Builder; +import com.google.api.client.http.HttpRequestInitializer; +import com.google.api.client.http.HttpTransport; +import com.google.api.client.json.JsonFactory; +import com.google.appengine.api.users.UserServiceFactory; + +import java.io.IOException; +import java.lang.reflect.Constructor; +import java.lang.reflect.InvocationTargetException; + +/** + * @author ngmiceli@google.com (Your Name Here) + * + */ +public class ServiceFactory { + + public static T createService( + Class service, OAuthContext context) + throws IOException { + for (Class builderClass : service.getDeclaredClasses()) { + if (AbstractGoogleJsonClient.Builder.class.isAssignableFrom(builderClass)) { + try { + Constructor constructor = builderClass.getConstructor( + HttpTransport.class, JsonFactory.class, HttpRequestInitializer.class); + Credential credential = context.getFlow() + .loadCredential(UserServiceFactory.getUserService().getCurrentUser().getUserId()); + // TODO(NOW): Do we want to allow the user to pass their own HttpRequestInitializer? + AbstractGoogleJsonClient.Builder builder = (Builder) constructor.newInstance( + context.getTransport(), context.getJsonFactory(), credential); + builder.setApplicationName(context.getUserAgent()); + @SuppressWarnings("unchecked") + T t = (T) builder.build(); + return t; + } catch (ClassCastException exception) { + } catch (NoSuchMethodException exception) { + } catch (SecurityException exception) { + } catch (InstantiationException exception) { + } catch (IllegalAccessException exception) { + } catch (IllegalArgumentException exception) { + } catch (InvocationTargetException exception) { + } + } + } + // TODO(NOW): Figure out the right way to handle all these exceptions. + throw new IllegalArgumentException(); + } + +} diff --git a/google-api-client-appengine/src/main/java/com/google/api/client/googleapis/extensions/appengine/utils/package-info.java b/google-api-client-appengine/src/main/java/com/google/api/client/googleapis/extensions/appengine/utils/package-info.java new file mode 100644 index 00000000..dd4e1c8a --- /dev/null +++ b/google-api-client-appengine/src/main/java/com/google/api/client/googleapis/extensions/appengine/utils/package-info.java @@ -0,0 +1,23 @@ +/* + * Copyright (c) 2013 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +/** + * Stuff + * + * @since 1.17 + * @author Nick Miceli + */ + +package com.google.api.client.googleapis.extensions.appengine.utils; + diff --git a/google-api-client-jackson2/src/main/java/com/google/api/client/googleapis/extensions/jackson2/auth/oauth2/GoogleOAuthInstalledAppContext.java b/google-api-client-jackson2/src/main/java/com/google/api/client/googleapis/extensions/jackson2/auth/oauth2/GoogleOAuthInstalledAppContext.java new file mode 100644 index 00000000..90d50a9b --- /dev/null +++ b/google-api-client-jackson2/src/main/java/com/google/api/client/googleapis/extensions/jackson2/auth/oauth2/GoogleOAuthInstalledAppContext.java @@ -0,0 +1,128 @@ +/* + * Copyright (c) 2013 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +package com.google.api.client.googleapis.extensions.jackson2.auth.oauth2; + +import com.google.api.client.auth.oauth2.AuthorizationCodeFlow; +import com.google.api.client.auth.oauth2.OAuthContext; +import com.google.api.client.googleapis.auth.oauth2.GoogleAuthorizationCodeFlow; +import com.google.api.client.googleapis.auth.oauth2.GoogleClientSecrets; +import com.google.api.client.http.HttpTransport; +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.client.util.store.DataStoreFactory; +import com.google.api.client.util.store.FileDataStoreFactory; + +import java.io.IOException; +import java.io.InputStreamReader; +import java.util.Collection; +import java.util.concurrent.locks.ReentrantLock; + +/** + * Thread safe OAuth 2.0 authorization context for installed application. It implements + * {@link OAuthContext} with default values such as + * {@link NetHttpTransport#getDefaultInstance} as the HTTP transport and + * {@link JacksonFactory#getDefaultInstance} as the JSON factory. + * + * @author Nick Miceli + * @author Eyal Peled + * + * @since 1.18 + */ +public class GoogleOAuthInstalledAppContext implements OAuthContext { + + private AuthorizationCodeFlow flow; + private GoogleClientSecrets clientSecrets; + + private final DataStoreFactory dataStoreFactory; + private final String clientSecretsPath; + private final Collection scopes; + private final String applicationName; + + private final ReentrantLock lock = new ReentrantLock(); + + /** + * Constructs a new OAuth context for installed applications. + * + * @param clientSecretsPath path to the client secrets Json file + * @param scopes scopes + * @param applicationName application name + */ + public GoogleOAuthInstalledAppContext( + String clientSecretsPath, Collection scopes, String applicationName) + throws IOException { + dataStoreFactory = new FileDataStoreFactory( + new java.io.File(System.getProperty("user.home"), ".store/" + applicationName)); + this.clientSecretsPath = clientSecretsPath; + this.applicationName = applicationName; + this.scopes = scopes; + } + + @Override + public HttpTransport getTransport() { + return NetHttpTransport.getDefaultInstance(); + } + + @Override + public JsonFactory getJsonFactory() { + return JacksonFactory.getDefaultInstance(); + } + + @Override + public AuthorizationCodeFlow getFlow() throws IOException { + lock.lock(); + try { + if (flow == null) { + flow = new GoogleAuthorizationCodeFlow.Builder(getTransport(), getJsonFactory(), + getClientSecrets(), getScopes()).setDataStoreFactory(getDataStoreFactory()) + .setAccessType("offline").build(); + } + } finally { + lock.unlock(); + } + return flow; + } + + @Override + public DataStoreFactory getDataStoreFactory() { + return dataStoreFactory; + } + + @Override + public String getUserAgent() { + return applicationName; + } + + @Override + public Collection getScopes() { + return scopes; + } + + /** + * Returns the Google client secrets which contains the client identifier and client secret + */ + protected GoogleClientSecrets getClientSecrets() throws IOException { + lock.lock(); + try { + if (clientSecrets == null) { + clientSecrets = GoogleClientSecrets.load(getJsonFactory(), new InputStreamReader( + GoogleOAuthInstalledAppContext.class.getResourceAsStream(clientSecretsPath))); + } + } finally { + lock.unlock(); + } + return clientSecrets; + } +} diff --git a/google-api-client-jackson2/src/main/java/com/google/api/client/googleapis/extensions/jackson2/auth/oauth2/package-info.java b/google-api-client-jackson2/src/main/java/com/google/api/client/googleapis/extensions/jackson2/auth/oauth2/package-info.java new file mode 100644 index 00000000..242f5220 --- /dev/null +++ b/google-api-client-jackson2/src/main/java/com/google/api/client/googleapis/extensions/jackson2/auth/oauth2/package-info.java @@ -0,0 +1,25 @@ +/* + * Copyright (c) 2013 Google Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file except + * in compliance with the License. You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software distributed under the License + * is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express + * or implied. See the License for the specific language governing permissions and limitations under + * the License. + */ + +/** + * {@link com.google.api.client.util.Beta}
+ * OAuth 2.0 utilities that help simplify the authorization flow on Google installed applications. + * + * @author Nick Miceli + * @author Eyal Peled + * @since 1.18 + */ +@com.google.api.client.util.Beta +package com.google.api.client.googleapis.extensions.jackson2.auth.oauth2; + diff --git a/google-api-client/src/main/java/com/google/api/client/googleapis/auth/oauth2/GoogleClientSecrets.java b/google-api-client/src/main/java/com/google/api/client/googleapis/auth/oauth2/GoogleClientSecrets.java index 4b42b4ff..d8e11af5 100644 --- a/google-api-client/src/main/java/com/google/api/client/googleapis/auth/oauth2/GoogleClientSecrets.java +++ b/google-api-client/src/main/java/com/google/api/client/googleapis/auth/oauth2/GoogleClientSecrets.java @@ -183,10 +183,21 @@ public GoogleClientSecrets clone() { /** * Loads the {@code client_secrets.json} file from the given reader. * + *

+ * Upgrade warning: in prior version 1.17 {@link #load} didn't throw + * {@link IllegalArgumentException} in case the client id or the client secret started with + * 'Enter', but starting with version 1.18 it will throw {@link IllegalArgumentException}. + *

+ * * @since 1.15 */ public static GoogleClientSecrets load(JsonFactory jsonFactory, Reader reader) throws IOException { - return jsonFactory.fromReader(reader, GoogleClientSecrets.class); + GoogleClientSecrets clientSecrets = jsonFactory.fromReader(reader, GoogleClientSecrets.class); + Preconditions.checkArgument(!clientSecrets.getDetails().getClientId().startsWith("Enter ") + && !clientSecrets.getDetails().getClientSecret().startsWith("Enter "), + "Download client_secrets.json file from https://code.google.com/apis/console/" + + " into your resources folder."); + return clientSecrets; } }