Skip to content

Commit 8f0ae3e

Browse files
PierrickVouletpierrick
andauthored
feat: add secured-app samples (#268)
Co-authored-by: pierrick <pierrick@google.com>
1 parent 3067d8a commit 8f0ae3e

File tree

18 files changed

+1570
-7
lines changed

18 files changed

+1570
-7
lines changed

java/chat/connectivity-app/src/main/java/com/google/chat/connectivityApp/App.java

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -26,7 +26,6 @@
2626
import com.google.apps.meet.v2.SpacesServiceSettings;
2727
import com.google.gson.JsonObject;
2828

29-
import java.util.Date;
3029
import java.util.Optional;
3130

3231
import org.springframework.boot.SpringApplication;

java/chat/preview-link/src/main/java/com/google/chat/previewLink/App.java

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -89,9 +89,8 @@ GenericJson handlePreviewLink(JsonNode chatMessage) {
8989
put("hostAppDataAction", new GenericJson() {{
9090
put("chatDataAction", new GenericJson() {{
9191
put("createMessageAction", new GenericJson() {{
92-
put("message", new GenericJson() {{
93-
put("text", "No matchedUrl detected.");
94-
}});
92+
put("message", new Message()
93+
.setText("No matchedUrl detected."));
9594
}});
9695
}});
9796
}});
@@ -105,9 +104,8 @@ GenericJson handlePreviewLink(JsonNode chatMessage) {
105104
put("hostAppDataAction", new GenericJson() {{
106105
put("chatDataAction", new GenericJson() {{
107106
put("createMessageAction", new GenericJson() {{
108-
put("message", new GenericJson() {{
109-
put("text", "event.chat.messagePayload.message.matchedUrl.url: " + chatMessage.at("/matchedUrl/url").asText());
110-
}});
107+
put("message", new Message()
108+
.setText("event.chat.messagePayload.message.matchedUrl.url: " + chatMessage.at("/matchedUrl/url").asText()));
111109
}});
112110
}});
113111
}});

java/chat/secured-app/README.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
# Google Chat secured app as Google Workspace add-on
2+
3+
This code sample demonstrates Google Workspace add-on request verification.
4+
5+
Please see related guide about
6+
[request verification](https://developers.google.com/workspace/add-ons/guides/alternate-runtimes#validate-requests-from-google).

java/chat/secured-app/pom.xml

Lines changed: 117 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,117 @@
1+
<?xml version="1.0" encoding="UTF-8"?>
2+
<!--
3+
Copyright 2025 Google LLC
4+
5+
Licensed under the Apache License, Version 2.0 (the "License");
6+
you may not use this file except in compliance with the License.
7+
You may obtain a copy of the License at
8+
9+
http://www.apache.org/licenses/LICENSE-2.0
10+
11+
Unless required by applicable law or agreed to in writing, software
12+
distributed under the License is distributed on an "AS IS" BASIS,
13+
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
See the License for the specific language governing permissions and
15+
limitations under the License.
16+
-->
17+
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
18+
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
19+
<modelVersion>4.0.0</modelVersion>
20+
21+
<name>secured-app</name>
22+
23+
<groupId>com.google.chat</groupId>
24+
<artifactId>secured-app</artifactId>
25+
<version>1.0-SNAPSHOT</version>
26+
27+
<properties>
28+
<maven.compiler.target>17</maven.compiler.target>
29+
<maven.compiler.source>17</maven.compiler.source>
30+
</properties>
31+
32+
<dependencyManagement>
33+
<dependencies>
34+
<dependency>
35+
<!-- Import dependency management from Spring Boot -->
36+
<groupId>org.springframework.boot</groupId>
37+
<artifactId>spring-boot-dependencies</artifactId>
38+
<version>3.2.3</version>
39+
<type>pom</type>
40+
<scope>import</scope>
41+
</dependency>
42+
43+
<dependency>
44+
<groupId>org.springframework.cloud</groupId>
45+
<artifactId>spring-cloud-dependencies</artifactId>
46+
<version>Greenwich.SR1</version>
47+
<type>pom</type>
48+
<scope>import</scope>
49+
</dependency>
50+
51+
<dependency>
52+
<groupId>com.fasterxml.jackson</groupId>
53+
<artifactId>jackson-bom</artifactId>
54+
<version>2.17.2</version> <type>pom</type>
55+
<scope>import</scope>
56+
</dependency>
57+
</dependencies>
58+
</dependencyManagement>
59+
60+
<dependencies>
61+
<dependency>
62+
<groupId>org.springframework.boot</groupId>
63+
<artifactId>spring-boot-starter-web</artifactId>
64+
<version>3.2.3</version>
65+
<exclusions>
66+
<!-- Exclude the Tomcat dependency -->
67+
<exclusion>
68+
<groupId>org.springframework.boot</groupId>
69+
<artifactId>spring-boot-starter-tomcat</artifactId>
70+
</exclusion>
71+
</exclusions>
72+
</dependency>
73+
74+
<dependency>
75+
<groupId>org.springframework.boot</groupId>
76+
<artifactId>spring-boot-starter-jetty</artifactId>
77+
<version>3.2.3</version>
78+
</dependency>
79+
80+
<dependency>
81+
<groupId>com.google.apis</groupId>
82+
<artifactId>google-api-services-chat</artifactId>
83+
<version>v1-rev20241008-2.0.0</version>
84+
</dependency>
85+
86+
<dependency>
87+
<groupId>com.fasterxml.jackson.core</groupId>
88+
<artifactId>jackson-databind</artifactId>
89+
</dependency>
90+
</dependencies>
91+
92+
<build>
93+
<plugins>
94+
<plugin>
95+
<groupId>org.springframework.boot</groupId>
96+
<artifactId>spring-boot-maven-plugin</artifactId>
97+
<version>3.2.3</version>
98+
<executions>
99+
<execution>
100+
<goals>
101+
<goal>repackage</goal>
102+
</goals>
103+
</execution>
104+
</executions>
105+
</plugin>
106+
107+
<plugin>
108+
<groupId>com.google.cloud.tools</groupId>
109+
<artifactId>appengine-maven-plugin</artifactId>
110+
<version>2.7.0</version>
111+
<configuration>
112+
<version>GCLOUD_CONFIG</version>
113+
</configuration>
114+
</plugin>
115+
</plugins>
116+
</build>
117+
</project>
Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,20 @@
1+
# Copyright 2025 Google LLC
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
runtime: java17
16+
instance_class: F1
17+
18+
# Explicitly set the memory limit and maximum heap size for the Spring Boot app
19+
env_variables:
20+
JAVA_TOOL_OPTIONS: "-XX:MaxRAM=256m -XX:ActiveProcessorCount=2 -Xmx32m"
Lines changed: 94 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,94 @@
1+
/**
2+
* Copyright 2025 Google LLC
3+
*
4+
* <p>Licensed under the Apache License, Version 2.0 (the "License"); you may not use this file
5+
* except in compliance with the License. You may obtain a copy of the License at
6+
*
7+
* <p>http://www.apache.org/licenses/LICENSE-2.0
8+
*
9+
* <p>Unless required by applicable law or agreed to in writing, software distributed under the
10+
* License is distributed on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
11+
* express or implied. See the License for the specific language governing permissions and
12+
* limitations under the License.
13+
*/
14+
package com.google.chat.app.secured;
15+
16+
import java.util.Collections;
17+
import java.util.logging.Logger;
18+
19+
import org.springframework.boot.SpringApplication;
20+
import org.springframework.boot.autoconfigure.SpringBootApplication;
21+
import org.springframework.web.bind.annotation.PostMapping;
22+
import org.springframework.web.bind.annotation.RequestBody;
23+
import org.springframework.web.bind.annotation.RequestHeader;
24+
import org.springframework.web.bind.annotation.ResponseBody;
25+
import org.springframework.web.bind.annotation.RestController;
26+
27+
import com.fasterxml.jackson.databind.JsonNode;
28+
import com.google.api.client.json.GenericJson;
29+
import com.google.api.client.googleapis.auth.oauth2.GoogleIdToken;
30+
import com.google.api.client.googleapis.auth.oauth2.GoogleIdTokenVerifier;
31+
import com.google.api.client.http.apache.ApacheHttpTransport;
32+
import com.google.api.client.json.JsonFactory;
33+
import com.google.api.client.json.jackson2.JacksonFactory;
34+
import com.google.api.services.chat.v1.model.Message;
35+
36+
@SpringBootApplication
37+
@RestController
38+
public class App {
39+
40+
// Service account email to verify requests from
41+
static String SERVICE_ACCOUNT_EMAIL = "your-add-on-service-account-email";
42+
43+
// Endpoint URL of the add-on
44+
static String HTTP_ENDPOINT = "your-add-on-endpoint-url";
45+
46+
private static final Logger logger = Logger.getLogger(App.class.getName());
47+
48+
public static void main(String[] args) {
49+
SpringApplication.run(App.class, args);
50+
}
51+
52+
// Returns a simple text message app response based on whether the request is verified or not.
53+
@PostMapping("/")
54+
@ResponseBody
55+
public GenericJson onEvent(
56+
@RequestBody JsonNode event, @RequestHeader("Authorization") String authorization)
57+
throws Exception {
58+
return new GenericJson() {{
59+
put("hostAppDataAction", new GenericJson() {{
60+
put("chatDataAction", new GenericJson() {{
61+
put("createMessageAction", new GenericJson() {{
62+
put("message", new Message()
63+
.setText(verifyAddOnRequest(event, authorization) ? "Successful verification!" : "Failed verification!"));
64+
}});
65+
}});
66+
}});
67+
}};
68+
}
69+
70+
// [START verify_add_on_request]
71+
/**
72+
* Determine whether a Google Workspace add-on request is legitimate.
73+
*
74+
* @param event Event sent from Google Workspace add-on
75+
* @param authorization Authorization header from the request
76+
* @return {boolean} Whether the request is legitimate
77+
*/
78+
private boolean verifyAddOnRequest(JsonNode event, String authorization) throws Exception {
79+
JsonFactory factory = JacksonFactory.getDefaultInstance();
80+
81+
GoogleIdTokenVerifier verifier =
82+
new GoogleIdTokenVerifier.Builder(new ApacheHttpTransport(), factory)
83+
.setAudience(Collections.singletonList(HTTP_ENDPOINT))
84+
.build();
85+
86+
String bearer = authorization.substring("Bearer ".length(), authorization.length());
87+
GoogleIdToken idToken = GoogleIdToken.parse(factory, bearer);
88+
return idToken != null
89+
&& verifier.verify(idToken)
90+
&& idToken.getPayload().getEmailVerified()
91+
&& idToken.getPayload().getEmail().equals(SERVICE_ACCOUNT_EMAIL);
92+
}
93+
// [END verify_add_on_request]
94+
}
Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
# Copyright 2025 Google LLC
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
# Set the port to the PORT environment variable
16+
server.port=${PORT:8080}
Lines changed: 17 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,17 @@
1+
# This file specifies files that are *not* uploaded to Google Cloud
2+
# using gcloud. It follows the same syntax as .gitignore, with the addition of
3+
# "#!include" directives (which insert the entries of the given .gitignore-style
4+
# file at that point).
5+
#
6+
# For more information, run:
7+
# $ gcloud topic gcloudignore
8+
#
9+
.gcloudignore
10+
# If you would like to upload your .git directory, .gitignore file or files
11+
# from your .gitignore file, remove the corresponding line
12+
# below:
13+
.git
14+
.gitignore
15+
16+
# Node.js dependencies:
17+
node_modules/

node/chat/secured-app/README.md

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
# Google Chat secured app as Google Workspace add-on
2+
3+
This code sample demonstrates Google Workspace add-on request verification.
4+
5+
Please see related guide about
6+
[request verification](https://developers.google.com/workspace/add-ons/guides/alternate-runtimes#validate-requests-from-google).

node/chat/secured-app/app.yaml

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
# Copyright 2025 Google LLC
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
15+
# This file specifies your Python application's runtime configuration.
16+
# See https://cloud.google.com/appengine/docs/managed-vms/python/runtime
17+
#
18+
19+
runtime: nodejs20

0 commit comments

Comments
 (0)