From 11d6a3cb30c4ebfe4fc4e196d99f5764c6ade878 Mon Sep 17 00:00:00 2001 From: Tomo Suzuki Date: Fri, 10 May 2024 13:26:06 -0400 Subject: [PATCH] feat: servlet classes that use the jakarta namespace (#1115) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * feat: servlet classes that use the jakarta namespace * 🦉 Updates from OwlBot post-processor See https://github.com/googleapis/repo-automation-bots/blob/main/packages/owl-bot/README.md * copyright --------- Co-authored-by: Owl Bot --- google-oauth-client-appengine/pom.xml | 5 + ...ngineAuthorizationCodeCallbackServlet.java | 94 +++++++++ ...ractAppEngineAuthorizationCodeServlet.java | 88 ++++++++ .../auth/oauth2/jakarta/package-info.java | 21 ++ google-oauth-client-servlet/pom.xml | 5 + ...tractAuthorizationCodeCallbackServlet.java | 188 +++++++++++++++++ .../AbstractAuthorizationCodeServlet.java | 191 ++++++++++++++++++ .../auth/oauth2/jakarta/package-info.java | 21 ++ pom.xml | 7 + 9 files changed, 620 insertions(+) create mode 100644 google-oauth-client-appengine/src/main/java/com/google/api/client/extensions/appengine/auth/oauth2/jakarta/AbstractAppEngineAuthorizationCodeCallbackServlet.java create mode 100644 google-oauth-client-appengine/src/main/java/com/google/api/client/extensions/appengine/auth/oauth2/jakarta/AbstractAppEngineAuthorizationCodeServlet.java create mode 100644 google-oauth-client-appengine/src/main/java/com/google/api/client/extensions/appengine/auth/oauth2/jakarta/package-info.java create mode 100644 google-oauth-client-servlet/src/main/java/com/google/api/client/extensions/servlet/auth/oauth2/jakarta/AbstractAuthorizationCodeCallbackServlet.java create mode 100644 google-oauth-client-servlet/src/main/java/com/google/api/client/extensions/servlet/auth/oauth2/jakarta/AbstractAuthorizationCodeServlet.java create mode 100644 google-oauth-client-servlet/src/main/java/com/google/api/client/extensions/servlet/auth/oauth2/jakarta/package-info.java diff --git a/google-oauth-client-appengine/pom.xml b/google-oauth-client-appengine/pom.xml index 49a9e02b1..a931fd110 100644 --- a/google-oauth-client-appengine/pom.xml +++ b/google-oauth-client-appengine/pom.xml @@ -123,6 +123,11 @@ junit test + + jakarta.servlet + jakarta.servlet-api + provided + javax.servlet servlet-api diff --git a/google-oauth-client-appengine/src/main/java/com/google/api/client/extensions/appengine/auth/oauth2/jakarta/AbstractAppEngineAuthorizationCodeCallbackServlet.java b/google-oauth-client-appengine/src/main/java/com/google/api/client/extensions/appengine/auth/oauth2/jakarta/AbstractAppEngineAuthorizationCodeCallbackServlet.java new file mode 100644 index 000000000..d49bcd9b9 --- /dev/null +++ b/google-oauth-client-appengine/src/main/java/com/google/api/client/extensions/appengine/auth/oauth2/jakarta/AbstractAppEngineAuthorizationCodeCallbackServlet.java @@ -0,0 +1,94 @@ +/* + * Copyright (c) 2024 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.extensions.appengine.auth.oauth2.jakarta; + +import com.google.api.client.extensions.servlet.auth.oauth2.jakarta.AbstractAuthorizationCodeCallbackServlet; +import com.google.appengine.api.users.UserServiceFactory; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import java.io.IOException; + +/** + * Simple extension of {@link AbstractAuthorizationCodeCallbackServlet} that uses the currently + * logged-in Google Account user, as directed in Security + * and Authentication. This uses the {@code jakarta.servlet} namespace. + * + *

Note that if there is no currently logged-in user, {@link #getUserId(HttpServletRequest)} will + * throw a {@link NullPointerException}. Example to require login for all pages: + * + *

+ * <security-constraint>
+ * <web-resource-collection>
+ * <web-resource-name>any</web-resource-name>
+ * <url-pattern>/*</url-pattern>
+ * </web-resource-collection>
+ * <auth-constraint>
+ * <role-name>*</role-name>
+ * </auth-constraint>
+ * </security-constraint>
+ * 
+ * + *

Sample usage: + * + *

+ * public class ServletCallbackSample extends AbstractAppEngineAuthorizationCodeCallbackServlet {
+ *
+ * @Override
+ * protected void onSuccess(HttpServletRequest req, HttpServletResponse resp, Credential credential)
+ * throws ServletException, IOException {
+ * resp.sendRedirect("/");
+ * }
+ *
+ * @Override
+ * protected void onError(
+ * HttpServletRequest req, HttpServletResponse resp, AuthorizationCodeResponseUrl errorResponse)
+ * throws ServletException, IOException {
+ * // handle error
+ * }
+ *
+ * @Override
+ * protected String getRedirectUri(HttpServletRequest req) throws ServletException, IOException {
+ * GenericUrl url = new GenericUrl(req.getRequestURL().toString());
+ * url.setRawPath("/oauth2callback");
+ * return url.build();
+ * }
+ *
+ * @Override
+ * protected AuthorizationCodeFlow initializeFlow() throws IOException {
+ * return new AuthorizationCodeFlow.Builder(BearerToken.authorizationHeaderAccessMethod(),
+ * new UrlFetchTransport(),
+ * new GsonFactory(),
+ * new GenericUrl("https://server.example.com/token"),
+ * new BasicAuthentication("s6BhdRkqt3", "7Fjfp0ZBr1KtDRbnfVdmIw"),
+ * "s6BhdRkqt3",
+ * "https://server.example.com/authorize").setCredentialStore(new AppEngineCredentialStore())
+ * .build();
+ * }
+ * 
+ * + * @since 1.36.0 + */ +public abstract class AbstractAppEngineAuthorizationCodeCallbackServlet + extends AbstractAuthorizationCodeCallbackServlet { + + private static final long serialVersionUID = 1L; + + @Override + protected String getUserId(HttpServletRequest req) throws ServletException, IOException { + // Use GAE Standard's users service to fetch the current user of the application. + return UserServiceFactory.getUserService().getCurrentUser().getUserId(); + } +} diff --git a/google-oauth-client-appengine/src/main/java/com/google/api/client/extensions/appengine/auth/oauth2/jakarta/AbstractAppEngineAuthorizationCodeServlet.java b/google-oauth-client-appengine/src/main/java/com/google/api/client/extensions/appengine/auth/oauth2/jakarta/AbstractAppEngineAuthorizationCodeServlet.java new file mode 100644 index 000000000..2f334f72e --- /dev/null +++ b/google-oauth-client-appengine/src/main/java/com/google/api/client/extensions/appengine/auth/oauth2/jakarta/AbstractAppEngineAuthorizationCodeServlet.java @@ -0,0 +1,88 @@ +/* + * Copyright (c) 2024 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.extensions.appengine.auth.oauth2.jakarta; + +import com.google.api.client.extensions.servlet.auth.oauth2.jakarta.AbstractAuthorizationCodeServlet; +import com.google.appengine.api.users.UserServiceFactory; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServletRequest; +import java.io.IOException; + +/** + * Simple extension of {@link AbstractAuthorizationCodeServlet} that uses the currently logged-in + * Google Account user, as directed in Security + * and Authentication. This uses the {@code jakarta.servlet} namespace. + * + *

Note that if there is no currently logged-in user, {@link #getUserId(HttpServletRequest)} will + * throw a {@link NullPointerException}. Example to require login for all pages: + * + *

+ * <security-constraint>
+ * <web-resource-collection>
+ * <web-resource-name>any</web-resource-name>
+ * <url-pattern>/*</url-pattern>
+ * </web-resource-collection>
+ * <auth-constraint>
+ * <role-name>*</role-name>
+ * </auth-constraint>
+ * </security-constraint>
+ * 
+ * + *

Sample usage: + * + *

+ * public class ServletSample extends AbstractAppEngineAuthorizationCodeServlet {
+ *
+ * @Override
+ * protected void doGet(HttpServletRequest request, HttpServletResponse response)
+ * throws IOException {
+ * // do stuff
+ * }
+ *
+ * @Override
+ * protected String getRedirectUri(HttpServletRequest req) throws ServletException, IOException {
+ * GenericUrl url = new GenericUrl(req.getRequestURL().toString());
+ * url.setRawPath("/oauth2callback");
+ * return url.build();
+ * }
+ *
+ * @Override
+ * protected AuthorizationCodeFlow initializeFlow() throws IOException {
+ * return new AuthorizationCodeFlow.Builder(BearerToken.authorizationHeaderAccessMethod(),
+ * new UrlFetchTransport(),
+ * new GsonFactory(),
+ * new GenericUrl("https://server.example.com/token"),
+ * new BasicAuthentication("s6BhdRkqt3", "7Fjfp0ZBr1KtDRbnfVdmIw"),
+ * "s6BhdRkqt3",
+ * "https://server.example.com/authorize").setCredentialStore(new AppEngineCredentialStore())
+ * .build();
+ * }
+ * }
+ * 
+ * + * @since 1.36.0 + */ +public abstract class AbstractAppEngineAuthorizationCodeServlet + extends AbstractAuthorizationCodeServlet { + + private static final long serialVersionUID = 1L; + + @Override + protected String getUserId(HttpServletRequest req) throws ServletException, IOException { + // Use GAE Standard's users service to fetch the current user of the application. + return UserServiceFactory.getUserService().getCurrentUser().getUserId(); + } +} diff --git a/google-oauth-client-appengine/src/main/java/com/google/api/client/extensions/appengine/auth/oauth2/jakarta/package-info.java b/google-oauth-client-appengine/src/main/java/com/google/api/client/extensions/appengine/auth/oauth2/jakarta/package-info.java new file mode 100644 index 000000000..2bd566f94 --- /dev/null +++ b/google-oauth-client-appengine/src/main/java/com/google/api/client/extensions/appengine/auth/oauth2/jakarta/package-info.java @@ -0,0 +1,21 @@ +/* + * Copyright (c) 2024 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. + */ + +/** + * OAuth 2.0 utilities that help simplify the authorization flow on Google App Engine. This package + * uses the {@code jakarta.servlet} namespace. + * + * @since 1.36.0 + */ +package com.google.api.client.extensions.appengine.auth.oauth2.jakarta; diff --git a/google-oauth-client-servlet/pom.xml b/google-oauth-client-servlet/pom.xml index 7e81a0e14..4fc16e7af 100644 --- a/google-oauth-client-servlet/pom.xml +++ b/google-oauth-client-servlet/pom.xml @@ -116,6 +116,11 @@ com.google.http-client google-http-client
+ + jakarta.servlet + jakarta.servlet-api + provided + javax.servlet servlet-api diff --git a/google-oauth-client-servlet/src/main/java/com/google/api/client/extensions/servlet/auth/oauth2/jakarta/AbstractAuthorizationCodeCallbackServlet.java b/google-oauth-client-servlet/src/main/java/com/google/api/client/extensions/servlet/auth/oauth2/jakarta/AbstractAuthorizationCodeCallbackServlet.java new file mode 100644 index 000000000..65f901bdf --- /dev/null +++ b/google-oauth-client-servlet/src/main/java/com/google/api/client/extensions/servlet/auth/oauth2/jakarta/AbstractAuthorizationCodeCallbackServlet.java @@ -0,0 +1,188 @@ +/* + * Copyright (c) 2024 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.extensions.servlet.auth.oauth2.jakarta; + +import com.google.api.client.auth.oauth2.AuthorizationCodeFlow; +import com.google.api.client.auth.oauth2.AuthorizationCodeResponseUrl; +import com.google.api.client.auth.oauth2.Credential; +import com.google.api.client.auth.oauth2.TokenResponse; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServlet; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; + +/** + * Thread-safe OAuth 2.0 authorization code callback servlet using the jakarta namespace to process + * the authorization code or error response from authorization page redirect. + * + *

This is designed to simplify the flow in which an end-user authorizes your web application to + * access their protected data. The main servlet class extends {@link + * AbstractAuthorizationCodeServlet} which if the end-user credentials are not found, will redirect + * the end-user to an authorization page. If the end-user grants authorization, they will be + * redirected to this servlet that extends {@link AbstractAuthorizationCodeCallbackServlet} and the + * {@link #onSuccess} will be called. Similarly, if the end-user grants authorization, they will be + * redirected to this servlet and {@link #onError} will be called. + * + *

Sample usage: + * + *

+ * public class ServletCallbackSample extends AbstractAuthorizationCodeCallbackServlet {
+ *
+ * @Override
+ * protected void onSuccess(HttpServletRequest req, HttpServletResponse resp, Credential credential)
+ * throws ServletException, IOException {
+ * resp.sendRedirect("/");
+ * }
+ *
+ * @Override
+ * protected void onError(
+ * HttpServletRequest req, HttpServletResponse resp, AuthorizationCodeResponseUrl errorResponse)
+ * throws ServletException, IOException {
+ * // handle error
+ * }
+ *
+ * @Override
+ * protected String getRedirectUri(HttpServletRequest req) throws ServletException, IOException {
+ * GenericUrl url = new GenericUrl(req.getRequestURL().toString());
+ * url.setRawPath("/oauth2callback");
+ * return url.build();
+ * }
+ *
+ * @Override
+ * protected AuthorizationCodeFlow initializeFlow() throws IOException {
+ * return new AuthorizationCodeFlow.Builder(BearerToken.authorizationHeaderAccessMethod(),
+ * new NetHttpTransport(),
+ * new GsonFactory(),
+ * new GenericUrl("https://server.example.com/token"),
+ * new BasicAuthentication("s6BhdRkqt3", "7Fjfp0ZBr1KtDRbnfVdmIw"),
+ * "s6BhdRkqt3",
+ * "https://server.example.com/authorize").setCredentialStore(
+ * new JdoCredentialStore(JDOHelper.getPersistenceManagerFactory("transactions-optional")))
+ * .build();
+ * }
+ *
+ * @Override
+ * protected String getUserId(HttpServletRequest req) throws ServletException, IOException {
+ * // return user ID
+ * }
+ * }
+ * 
+ * + * @since 1.36.0 + */ +public abstract class AbstractAuthorizationCodeCallbackServlet extends HttpServlet { + + private static final long serialVersionUID = 1L; + + /** Lock on the flow. */ + private final Lock lock = new ReentrantLock(); + + /** + * Authorization code flow to be used across all HTTP servlet requests or {@code null} before + * initialized in {@link #initializeFlow()}. + */ + private AuthorizationCodeFlow flow; + + @Override + protected final void doGet(HttpServletRequest req, HttpServletResponse resp) + throws ServletException, IOException { + StringBuffer buf = req.getRequestURL(); + if (req.getQueryString() != null) { + buf.append('?').append(req.getQueryString()); + } + AuthorizationCodeResponseUrl responseUrl = new AuthorizationCodeResponseUrl(buf.toString()); + String code = responseUrl.getCode(); + if (responseUrl.getError() != null) { + onError(req, resp, responseUrl); + } else if (code == null) { + resp.setStatus(HttpServletResponse.SC_BAD_REQUEST); + resp.getWriter().print("Missing authorization code"); + } else { + lock.lock(); + try { + if (flow == null) { + flow = initializeFlow(); + } + String redirectUri = getRedirectUri(req); + TokenResponse response = flow.newTokenRequest(code).setRedirectUri(redirectUri).execute(); + String userId = getUserId(req); + Credential credential = flow.createAndStoreCredential(response, userId); + onSuccess(req, resp, credential); + } finally { + lock.unlock(); + } + } + } + + /** + * Loads the authorization code flow to be used across all HTTP servlet requests (only called + * during the first HTTP servlet request with an authorization code). + */ + protected abstract AuthorizationCodeFlow initializeFlow() throws ServletException, IOException; + + /** Returns the redirect URI for the given HTTP servlet request. */ + protected abstract String getRedirectUri(HttpServletRequest req) + throws ServletException, IOException; + + /** + * Returns the user ID for the given HTTP servlet request. This identifies your application's user + * and is used to assign and persist credentials to that user. Most commonly, this will be a user + * id stored in the session or even the session id itself. + */ + protected abstract String getUserId(HttpServletRequest req) throws ServletException, IOException; + + /** + * Handles a successfully granted authorization. + * + *

Default implementation is to do nothing, but subclasses should override and implement. + * Sample implementation: + * + *

+   * resp.sendRedirect("/granted");
+   * 
+ * + * @param req HTTP servlet request + * @param resp HTTP servlet response + * @param credential credential + * @throws ServletException HTTP servlet exception + * @throws IOException some I/O exception + */ + protected void onSuccess(HttpServletRequest req, HttpServletResponse resp, Credential credential) + throws ServletException, IOException {} + + /** + * Handles an error to the authorization, such as when an end user denies authorization. + * + *

Default implementation is to do nothing, but subclasses should override and implement. + * Sample implementation: + * + *

+   * resp.sendRedirect("/denied");
+   * 
+ * + * @param req HTTP servlet request + * @param resp HTTP servlet response + * @param errorResponse error response ({@link AuthorizationCodeResponseUrl#getError()} is not + * {@code null}) + * @throws ServletException HTTP servlet exception + * @throws IOException some I/O exception + */ + protected void onError( + HttpServletRequest req, HttpServletResponse resp, AuthorizationCodeResponseUrl errorResponse) + throws ServletException, IOException {} +} diff --git a/google-oauth-client-servlet/src/main/java/com/google/api/client/extensions/servlet/auth/oauth2/jakarta/AbstractAuthorizationCodeServlet.java b/google-oauth-client-servlet/src/main/java/com/google/api/client/extensions/servlet/auth/oauth2/jakarta/AbstractAuthorizationCodeServlet.java new file mode 100644 index 000000000..64e208694 --- /dev/null +++ b/google-oauth-client-servlet/src/main/java/com/google/api/client/extensions/servlet/auth/oauth2/jakarta/AbstractAuthorizationCodeServlet.java @@ -0,0 +1,191 @@ +/* + * Copyright (c) 2024 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.extensions.servlet.auth.oauth2.jakarta; + +import com.google.api.client.auth.oauth2.AuthorizationCodeFlow; +import com.google.api.client.auth.oauth2.AuthorizationCodeRequestUrl; +import com.google.api.client.auth.oauth2.Credential; +import com.google.api.client.http.HttpResponseException; +import jakarta.servlet.ServletException; +import jakarta.servlet.http.HttpServlet; +import jakarta.servlet.http.HttpServletRequest; +import jakarta.servlet.http.HttpServletResponse; +import java.io.IOException; +import java.util.concurrent.locks.Lock; +import java.util.concurrent.locks.ReentrantLock; + +/** + * Thread-safe OAuth 2.0 authorization code flow HTTP servlet using the jakarta namespace that + * manages and persists end-user credentials. + * + *

This is designed to simplify the flow in which an end-user authorizes your web application to + * access their protected data. Your application then has access to their data based on an access + * token and a refresh token to refresh that access token when it expires. Your main servlet class + * should extend {@link AbstractAuthorizationCodeServlet} and implement the abstract methods. To get + * the persisted credential associated with the current request, call {@link #getCredential()}. It + * is assumed that the end-user is authenticated by some external means by which a user ID is + * obtained. This user ID is used as the primary key for persisting the end-user credentials, and + * passed in via {@link #getUserId(HttpServletRequest)}. The first time an end-user arrives at your + * servlet, they will be redirected in the browser to an authorization page. Next, they will be + * redirected back to your site at the redirect URI selected in {@link + * #getRedirectUri(HttpServletRequest)}. The servlet to process that should extend {@link + * AbstractAuthorizationCodeCallbackServlet}, which should redirect back to this servlet on success. + * + *

Although this implementation is thread-safe, it can only process one request at a time. For a + * more performance-critical multi-threaded web application, instead use {@link + * AuthorizationCodeFlow} directly. + * + *

Sample usage: + * + *

+ * public class ServletSample extends AbstractAuthorizationCodeServlet {
+ *
+ * @Override
+ * protected void doGet(HttpServletRequest request, HttpServletResponse response)
+ * throws IOException {
+ * // do stuff
+ * }
+ *
+ * @Override
+ * protected String getRedirectUri(HttpServletRequest req) throws ServletException, IOException {
+ * GenericUrl url = new GenericUrl(req.getRequestURL().toString());
+ * url.setRawPath("/oauth2callback");
+ * return url.build();
+ * }
+ *
+ * @Override
+ * protected AuthorizationCodeFlow initializeFlow() throws IOException {
+ * return new AuthorizationCodeFlow.Builder(BearerToken.authorizationHeaderAccessMethod(),
+ * new NetHttpTransport(),
+ * new GsonFactory(),
+ * new GenericUrl("https://server.example.com/token"),
+ * new BasicAuthentication("s6BhdRkqt3", "7Fjfp0ZBr1KtDRbnfVdmIw"),
+ * "s6BhdRkqt3",
+ * "https://server.example.com/authorize").setCredentialStore(
+ * new JdoCredentialStore(JDOHelper.getPersistenceManagerFactory("transactions-optional")))
+ * .build();
+ * }
+ *
+ * @Override
+ * protected String getUserId(HttpServletRequest req) throws ServletException, IOException {
+ * // return user ID
+ * }
+ * }
+ * 
+ * + * @since 1.36.0 + */ +public abstract class AbstractAuthorizationCodeServlet extends HttpServlet { + + private static final long serialVersionUID = 1L; + + /** Lock on the flow and credential. */ + private final Lock lock = new ReentrantLock(); + + /** Persisted credential associated with the current request or {@code null} for none. */ + private Credential credential; + + /** + * Authorization code flow to be used across all HTTP servlet requests or {@code null} before + * initialized in {@link #initializeFlow()}. + */ + private AuthorizationCodeFlow flow; + + @Override + protected void service(HttpServletRequest req, HttpServletResponse resp) + throws IOException, ServletException { + lock.lock(); + try { + // load credential from persistence store + String userId = getUserId(req); + if (flow == null) { + flow = initializeFlow(); + } + credential = flow.loadCredential(userId); + // if credential found with an access token, invoke the user code + if (credential != null && credential.getAccessToken() != null) { + try { + super.service(req, resp); + return; + } catch (HttpResponseException e) { + // if access token is null, assume it is because auth failed and we need to re-authorize + // but if access token is not null, it is some other problem + if (credential.getAccessToken() != null) { + throw e; + } + } + } + // redirect to the authorization flow + AuthorizationCodeRequestUrl authorizationUrl = flow.newAuthorizationUrl(); + authorizationUrl.setRedirectUri(getRedirectUri(req)); + onAuthorization(req, resp, authorizationUrl); + credential = null; + } finally { + lock.unlock(); + } + } + + /** + * Loads the authorization code flow to be used across all HTTP servlet requests (only called + * during the first HTTP servlet request). + */ + protected abstract AuthorizationCodeFlow initializeFlow() throws ServletException, IOException; + + /** Returns the redirect URI for the given HTTP servlet request. */ + protected abstract String getRedirectUri(HttpServletRequest req) + throws ServletException, IOException; + + /** + * Returns the user ID for the given HTTP servlet request. This identifies your application's user + * and is used to fetch persisted credentials for that user. Most commonly, this will be a user id + * stored in the session or even the session id itself. + */ + protected abstract String getUserId(HttpServletRequest req) throws ServletException, IOException; + + /** + * Return the persisted credential associated with the current request or {@code null} for none. + */ + protected final Credential getCredential() { + return credential; + } + + /** + * Handles user authorization by redirecting to the OAuth 2.0 authorization server. + * + *

Default implementation is to call {@code resp.sendRedirect(authorizationUrl.build())}. + * Subclasses may override to provide optional parameters such as the recommended state parameter. + * Sample implementation: + * + *

+   * @Override
+   * protected void onAuthorization(HttpServletRequest req, HttpServletResponse resp,
+   * AuthorizationCodeRequestUrl authorizationUrl) throws ServletException, IOException {
+   * authorizationUrl.setState("xyz");
+   * super.onAuthorization(req, resp, authorizationUrl);
+   * }
+   * 
+ * + * @param authorizationUrl authorization code request URL + * @param req HTTP servlet request + * @throws ServletException servlet exception + */ + protected void onAuthorization( + HttpServletRequest req, + HttpServletResponse resp, + AuthorizationCodeRequestUrl authorizationUrl) + throws ServletException, IOException { + resp.sendRedirect(authorizationUrl.build()); + } +} diff --git a/google-oauth-client-servlet/src/main/java/com/google/api/client/extensions/servlet/auth/oauth2/jakarta/package-info.java b/google-oauth-client-servlet/src/main/java/com/google/api/client/extensions/servlet/auth/oauth2/jakarta/package-info.java new file mode 100644 index 000000000..fe0a4c72c --- /dev/null +++ b/google-oauth-client-servlet/src/main/java/com/google/api/client/extensions/servlet/auth/oauth2/jakarta/package-info.java @@ -0,0 +1,21 @@ +/* + * Copyright (c) 2024 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. + */ + +/** + * OAuth 2.0 utilities that help simplify the authorization flow in HTTP servlets in the {@code + * jakarta.servlet} namespace. + * + * @since 1.36.0 + */ +package com.google.api.client.extensions.servlet.auth.oauth2.jakarta; diff --git a/pom.xml b/pom.xml index 91d7fafea..f56bb4092 100644 --- a/pom.xml +++ b/pom.xml @@ -116,6 +116,11 @@ jdo2-api ${project.jdo2-api.version}
+ + jakarta.servlet + jakarta.servlet-api + ${project.jakarta-servlet-api.version} + javax.servlet servlet-api @@ -462,6 +467,8 @@ 2.0.6 UTF-8 1.43.3 + + 5.0.0 3.0.2 31.1-android 1.1.4c