Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

appengine/oauth2 sample #168

Merged
merged 3 commits into from
Apr 20, 2016
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 4 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -26,3 +26,7 @@ dependency-reduced-pom.xml
buildNumber.properties

service-account.json

# intellij
.idea/
*.iml
7 changes: 7 additions & 0 deletions appengine/oauth2/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# Eclipse files
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Any reason to put this here instead of the root .gitignore?

.project
.classpath
.settings

# Target folders
target/
41 changes: 41 additions & 0 deletions appengine/oauth2/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# Google App Engine Standard Environment
## Oauth2 Sample

This sample demonstrates using the Oauth2 apis to create an authenticaion filter.

See the [Google App Engine standard environment documentation][ae-docs] for more
detailed instructions.


## Setup
1. In the [Cloud Developers Console](https://cloud.google.com/console) > API Manager > Credentials,
create a Oauth Client ID for a Web Application. You will need to provide an authroized JavaScript
origin. Typically, https://projectID.appspot.com.
1. Edit `src/main/webapp/index.html` and change `YOUR_CLIENT_ID_HERE.apps.googleusercontent.com` to
Client ID from the prior step.

## Running locally
NOTE: The app can be run locally, but the Oauth2 APIs do not work with the development server.

$ mvn appengine:devserver

## Deploying
$ mvn appengine:update -Dappengine.appId=YOUR-PROJECT-ID -Dappengine.version=SOME-VERSION

1. Using your browser, visit `https://YOUR-PROJECT-ID.appspot.com`, click Sign In.

1. The Sign In process will then request some text from your app, and then display it, if
the id matches the list in `src/main/java/com/example/appengine/Oauth2Filter.java`.

## Adding you to the list of valid users
NOTE: Typically, you would use this for Service Accounts, but user accounts work as well.

1. Enable logging by uncommenting the context.log line in
`src/main/java/com/example/appengine/Oauth2Filter.java`, redeploy, and visit the page
1. Look at the logs in [Cloud Developers Console](https://cloud.google.com/console) > Logs.

1. Add the `tokenAudience` to the `allowedClients`.

1. Deploy and visit the page again.

[ae-docs]: https://cloud.google.com/appengine/docs/java/
60 changes: 60 additions & 0 deletions appengine/oauth2/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
<!--
Copyright 2015 Google Inc. All Rights Reserved.

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.
-->
<project>
<modelVersion>4.0.0</modelVersion>
<packaging>war</packaging>
<version>1.0-SNAPSHOT</version>
<groupId>com.example.appengine</groupId>
<artifactId>appengine-oauth2</artifactId>

<properties>
<appengine.sdk.version>1.9.34</appengine.sdk.version>
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nit, optional] shouldn't need to specify appengine.sdk.version, since the parent also defines such a property. We can keep it here if we want pom.xml to work (mostly) without the parent present.

<maven.compiler.target>1.7</maven.compiler.target>
<maven.compiler.source>1.7</maven.compiler.source>
</properties>
<parent>
<groupId>com.google.cloud</groupId>
<artifactId>doc-samples</artifactId>
<version>1.0.0</version>
<relativePath>../..</relativePath>
</parent>

<dependencies>
<dependency>
<groupId>com.google.appengine</groupId>
<artifactId>appengine-api-1.0-sdk</artifactId>
<version>${appengine.sdk.version}</version>
</dependency>
<dependency>
<groupId>javax.servlet</groupId>
<artifactId>servlet-api</artifactId>
<type>jar</type>
<scope>provided</scope>
</dependency>
</dependencies>

<build>
<!-- for hot reload of the web application -->
<outputDirectory>${project.build.directory}/${project.build.finalName}/WEB-INF/classes</outputDirectory>
<plugins>
<plugin>
<groupId>com.google.appengine</groupId>
<artifactId>appengine-maven-plugin</artifactId>
<version>${appengine.sdk.version}</version>
</plugin>
</plugins>
</build>
</project>
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/**
* Copyright 2015 Google Inc. All Rights Reserved.
*
* 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.example.appengine;

import java.io.IOException;
import java.io.PrintWriter;

import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

// [START example]
@SuppressWarnings("serial")
public class HelloServlet extends HttpServlet {

@Override
public void doPost(final HttpServletRequest req, final HttpServletResponse resp)
throws IOException {
PrintWriter out = resp.getWriter();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nit, optional] The default content type is HTML, I believe, so technically we are outputting invalid HTML. Maybe set content type to text/plain?

out.print("Hello, world"); // simple hello world response
}
}
// [END example]
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
/*
* Copyright 2016 Google Inc. All Rights Reserved.
*
* 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.example.appengine;

import static com.google.appengine.api.utils.SystemProperty.environment;

import com.google.appengine.api.oauth.OAuthRequestException;
import com.google.appengine.api.oauth.OAuthService;
import com.google.appengine.api.oauth.OAuthServiceFactory;
import com.google.appengine.api.oauth.OAuthServiceFailureException;
import com.google.appengine.api.users.User;
import com.google.appengine.api.utils.SystemProperty;

import java.io.IOException;
import java.util.HashSet;
import java.util.Set;

import javax.servlet.Filter;
import javax.servlet.FilterChain;
import javax.servlet.FilterConfig;
import javax.servlet.ServletContext;
import javax.servlet.ServletException;
import javax.servlet.ServletRequest;
import javax.servlet.ServletResponse;
import javax.servlet.http.HttpServletResponse;

/**
* Filter to verify that request has a "Authorization: Bearer xxxx" header,
* and check if xxxx is authorized to use this app.
*
* Note - this is to demonstrate the OAuth2 APIs, as it is possible to lockdown some
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Javadoc error. Should be <p>Note for new paragraph.

* of your app's URL's using cloud console by adding service accounts to the project.
*/
public class Oauth2Filter implements Filter {

private ServletContext context;

@Override
public void init(final FilterConfig config) throws ServletException {
this.context = config.getServletContext();
}

// [START oauth2]
@Override
public void doFilter(final ServletRequest servletReq, final ServletResponse servletResp,
final FilterChain chain) throws IOException, ServletException {
Copy link
Contributor

@tswast tswast Apr 20, 2016

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

line continuation looks weird. We usually indent exactly 4 spaces for line continuations.

I'd also expect to break before final ServletRequest to follow the "rectangle rule".

final String scope = "https://www.googleapis.com/auth/userinfo.email";
Set<String> allowedClients = new HashSet<>();
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could we use an ImmutableSet here and add the clients in the builder? That is, does allowedClients need to be mutable? If not, we could even make this a private static final constant.


HttpServletResponse resp = (HttpServletResponse) servletResp;

OAuthService oauth = OAuthServiceFactory.getOAuthService();

allowedClients.add("407408718192.apps.googleusercontent.com"); // list of client ids to allow
allowedClients.add("755878275993-j4k7emq6rlupctce1c28enpcrr50vfo1.apps.googleusercontent.com");

// Only check Oauth2 when in production, skip if run in development.
SystemProperty.Environment.Value env = environment.value();
if (env == SystemProperty.Environment.Value.Production) { // APIs only work in Production
try {
User user = oauth.getCurrentUser(scope);
String tokenAudience = oauth.getClientId(scope);

// The line below is commented out for privacy.
// context.log("tokenAudience: " + tokenAudience); // Account we match

if (!allowedClients.contains(tokenAudience)) {
throw new OAuthRequestException("audience of token '" + tokenAudience
+ "' is not in allowed list " + allowedClients);
}
} catch (OAuthRequestException ex) {
resp.sendError(HttpServletResponse.SC_NOT_FOUND); // Not allowed
return;
} catch (OAuthServiceFailureException ex) {
resp.sendError(HttpServletResponse.SC_NOT_FOUND); // some failure - reject
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

SC_INTERNAL_SERVER_ERROR?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I prefer NOT_FOUND when it's auth related - ie lie. I am open to argument, however.

context.log("oauth2 failure", ex);
return;
}
}
chain.doFilter(servletReq, servletResp); // continue processing
}
// [END oauth2]

@Override
public void destroy() { }

}
20 changes: 20 additions & 0 deletions appengine/oauth2/src/main/webapp/WEB-INF/appengine-web.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- [START_EXCLUDE] -->
<!--
Copyright 2016 Google Inc. All Rights Reserved.
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.
-->
<!-- [END_EXCLUDE] -->
<appengine-web-app xmlns="http://appengine.google.com/ns/1.0">
<application>YOUR-PROJECT-ID</application>
<version>YOUR-VERSION-ID</version>
<threadsafe>true</threadsafe>
</appengine-web-app>
27 changes: 27 additions & 0 deletions appengine/oauth2/src/main/webapp/WEB-INF/web.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
<?xml version="1.0" encoding="utf-8"?>
<web-app xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://java.sun.com/xml/ns/javaee"
xmlns:web="http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
xsi:schemaLocation="http://java.sun.com/xml/ns/javaee http://java.sun.com/xml/ns/javaee/web-app_2_5.xsd"
version="2.5">
<filter>
<filter-name>Oauth2Filter</filter-name>
<filter-class>com.example.appengine.Oauth2Filter</filter-class>
</filter>
<filter-mapping>
<filter-name>Oauth2Filter</filter-name>
<url-pattern>/hello</url-pattern>
</filter-mapping>

<servlet>
<servlet-name>hello</servlet-name>
<servlet-class>com.example.appengine.HelloServlet</servlet-class>
</servlet>
<servlet-mapping>
<servlet-name>hello</servlet-name>
<url-pattern>/hello</url-pattern>
</servlet-mapping>

<welcome-file-list>
<welcome-file>index.html</welcome-file>
</welcome-file-list>
</web-app>
36 changes: 36 additions & 0 deletions appengine/oauth2/src/main/webapp/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
<!DOCTYPE html>
<html lang="en">
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

License header?

<head>
<meta name="google-signin-scope" content="profile">
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

missing required elements: title and meta tag for UTF-8 encoding.

<meta name="google-signin-client_id" content="YOUR_CLIENT_ID_HERE.apps.googleusercontent.com" />

<script src="https://apis.google.com/js/platform.js" async defer></script>
<script>
function onSignIn(googleUser) {
// Useful data for your client-side scripts:
var access_token = googleUser.getAuthResponse().access_token;


var xhr = new XMLHttpRequest();
xhr.open('POST', '/hello');
xhr.setRequestHeader("Authorization", "Bearer "+access_token);
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

[nit] I think JS style requires spaces around + operator.

xhr.setRequestHeader('Content-Type', 'application/x-www-form-urlencoded');
xhr.onload = function() {
document.getElementById('response').innerHTML = xhr.responseText;
document.getElementById('me').style = "display:inline;";
};
xhr.onError = function() {
document.getElementById('response').innerHTML = "Error";
document.getElementById('me').style = "display:inline;";
};
xhr.send();
}
</script>
</head>
<body>
<div class="g-signin2" data-onsuccess="onSignIn" data-theme="dark"></div>
<div id="me" style="display: none;">
<h1><span id="response"></span></h1>
</div>
</body>
</html>
3 changes: 3 additions & 0 deletions appengine/urlfetch/pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,14 @@ Copyright 2015 Google Inc. All Rights Reserved.
<version>1.0-SNAPSHOT</version>
<groupId>com.example.appengine</groupId>
<artifactId>appengine-URLFetch</artifactId>

<parent>
<groupId>com.google.cloud</groupId>
<artifactId>doc-samples</artifactId>
<version>1.0.0</version>
<relativePath>../..</relativePath>
</parent>

<dependencies>
<dependency>
<groupId>javax.servlet</groupId>
Expand All @@ -38,6 +40,7 @@ Copyright 2015 Google Inc. All Rights Reserved.
<version>20160212</version>
</dependency>
</dependencies>

<build>
<!-- for hot reload of the web application -->
<outputDirectory>${project.build.directory}/${project.build.finalName}/WEB-INF/classes</outputDirectory>
Expand Down
1 change: 1 addition & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,7 @@
<module>appengine/mailgun</module>
<module>appengine/mailjet</module>
<module>appengine/memcache</module>
<module>appengine/oauth2</module>
<module>appengine/sendgrid</module>
<module>appengine/static-files</module>
<module>appengine/twilio</module>
Expand Down