diff --git a/.github/workflows/build-jar.yml b/.github/workflows/build-jar.yml new file mode 100644 index 0000000..60674d5 --- /dev/null +++ b/.github/workflows/build-jar.yml @@ -0,0 +1,29 @@ +# This workflow will build a package using Maven and then publish it to GitHub packages when a release is created +# For more information see: https://github.com/actions/setup-java/blob/main/docs/advanced-usage.md#apache-maven-with-a-settings-path + +name: gmail-oauth2-tools + +on: push + +jobs: + build: + + runs-on: ubuntu-latest + permissions: + contents: read + packages: write + + steps: + - uses: actions/checkout@v3 + - name: Set up JDK 11 + uses: actions/setup-java@v3 + with: + java-version: '11' + distribution: 'temurin' + server-id: github # Value of the distributionManagement/repository/id field of the pom.xml + settings-path: ${{ github.workspace }}/java # location for the settings.xml file + + - name: Build with Maven + run: mvn -B package --file pom.xml + - name: Test with Maven + run: mvn test --file pom.xml diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..64ee209 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +target/ +.vscode/ \ No newline at end of file diff --git a/README.md b/README.md index 6264733..5e07690 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,5 @@ +[![gmail-oauth2-tools](https://github.com/FreedomFaighter/gmail-oauth2-tools/actions/workflows/build-jar.yml/badge.svg)](https://github.com/FreedomFaighter/gmail-oauth2-tools/actions/workflows/build-jar.yml) + Tools and sample code for authenticating to Gmail with OAuth2. The specification is available [here](https://developers.google.com/gmail/xoauth2_protocol). diff --git a/go/sendgmail/README.md b/go/sendgmail/README.md index 6a70407..2bb91dd 100644 --- a/go/sendgmail/README.md +++ b/go/sendgmail/README.md @@ -22,8 +22,9 @@ send-email`. * Follow the steps in the **Authorize credentials for a desktop application** section. However, set the application type to *Web application* (i.e. instead of *Desktop app*) and then add - `https://oauth2.dance/` as an authorised redirect URI. This is necessary - for seeing the authorisation code on a page in your browser. + `https://google.github.io/gmail-oauth2-tools/html/oauth2.dance.html` + as an authorised redirect URI. This is necessary for seeing the + authorisation code on a page in your browser. * When you download the credentials as JSON, create the `${XDG_CONFIG_HOME:-${HOME}/.config}/sendgmail` directory with file mode diff --git a/html/oauth2.dance.html b/html/oauth2.dance.html new file mode 100644 index 0000000..639d37a --- /dev/null +++ b/html/oauth2.dance.html @@ -0,0 +1,30 @@ + + + + +oauth2.dance +

oauth2.dance

+ + + diff --git a/java/build-java-sample-zip.sh b/java/build-java-sample-zip.sh deleted file mode 100755 index 70fe685..0000000 --- a/java/build-java-sample-zip.sh +++ /dev/null @@ -1,70 +0,0 @@ -#!/bin/sh -# -# Copyright 2012 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. - - -# Usage: build-java-sample-zip.sh [datestr] -# -# Copies the Java sample files to a temporary directory and zips them up. -# The resulting ZIP file is left in /tmp. -# If datestr is not specified, it defaults to the current date in YYYYMMDD -# format; for example, "20120904". This would result in the zipfile -# /tmp/oauth2-java-sample-20120904.zip. -# -# The script is intended to be run from the root of the Java code hierarchy. -# It makes sure there are no local modifications to the files that are -# being zipped up. - - -top_level_files="README-java-sample.txt build.xml build.properties ../python/oauth2.py" -relative_files="com/google/code/samples/oauth2/*.java" -all_files="$top_level_files $relative_files" - -if [[ "$1" ]] ; then - date="$1" -else - date=$(date "+%Y%m%d") -fi - -relative_tmpdir="oauth2-java-sample-$date" -full_tmpdir="/tmp/$relative_tmpdir" -outfile="/tmp/oauth2-java-sample-$date.zip" - -if [[ -e $full_tmpdir ]]; then - echo "ERROR: directory $full_tmpdir already exists" - exit -1 -fi - -if [[ -e $outfile ]]; then - echo "ERROR: $outfile already exists" - exit -1 -fi - -status=$(svn status $all_files) -if [[ "$status" ]] ; then - echo "ERROR: One or more files has uncommitted changes:" - echo "$status" - exit -1 -fi - -mkdir -p $full_tmpdir -cp $top_level_files $full_tmpdir -cp --parents $relative_files $full_tmpdir - -cd /tmp -zip -r $outfile $relative_tmpdir -rm -r $full_tmpdir - -echo "Created $outfile" diff --git a/java/build.properties b/java/build.properties deleted file mode 100644 index 5eabffc..0000000 --- a/java/build.properties +++ /dev/null @@ -1,34 +0,0 @@ -# Copyright 2012 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. - - - -# If you are not running Java EE, you may need to supply a JAR file containing -# the Java Mail package (see http://java.sun.com/products/javamail/). -javamail_jar= - -# These are commandline parameters for OAuth2Authenticator.main(). -email= -oauthToken= - -# -# You shouldn't need to change anything below this point. -# - -out=./out -classes=./classes -src=./ - -# The generated JAR file will go here. -oauth2_jar=${out}/oauth2.jar diff --git a/java/build.xml b/java/build.xml deleted file mode 100644 index 5ea1cd5..0000000 --- a/java/build.xml +++ /dev/null @@ -1,58 +0,0 @@ - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - - diff --git a/java/com/google/code/samples/oauth2/OAuth2Authenticator.java b/java/src/main/com/google/code/samples/oauth2/OAuth2Authenticator.java similarity index 96% rename from java/com/google/code/samples/oauth2/OAuth2Authenticator.java rename to java/src/main/com/google/code/samples/oauth2/OAuth2Authenticator.java index 596c5fc..254d7ad 100644 --- a/java/com/google/code/samples/oauth2/OAuth2Authenticator.java +++ b/java/src/main/com/google/code/samples/oauth2/OAuth2Authenticator.java @@ -13,7 +13,7 @@ * limitations under the License. */ -package com.google.code.samples.oauth2; +package main.com.google.code.samples.oauth2; import com.sun.mail.imap.IMAPStore; import com.sun.mail.imap.IMAPSSLStore; @@ -39,7 +39,7 @@ public class OAuth2Authenticator { Logger.getLogger(OAuth2Authenticator.class.getName()); public static final class OAuth2Provider extends Provider { - private static final long serialVersionUID = 1L; + private static final long serialVersionUID = 454502445710734267L; public OAuth2Provider() { super("Google OAuth2 Provider", 1.0, @@ -147,7 +147,7 @@ public static void main(String args[]) throws Exception { email, oauthToken, true); - System.out.println("Successfully authenticated to IMAP.\n"); + System.out.print("Successfully authenticated to IMAP.\n\n"); SMTPTransport smtpTransport = connectToSmtp("smtp.gmail.com", 587, email, diff --git a/java/com/google/code/samples/oauth2/OAuth2SaslClient.java b/java/src/main/com/google/code/samples/oauth2/OAuth2SaslClient.java similarity index 70% rename from java/com/google/code/samples/oauth2/OAuth2SaslClient.java rename to java/src/main/com/google/code/samples/oauth2/OAuth2SaslClient.java index 2bfef8d..43b1247 100644 --- a/java/com/google/code/samples/oauth2/OAuth2SaslClient.java +++ b/java/src/main/com/google/code/samples/oauth2/OAuth2SaslClient.java @@ -13,20 +13,20 @@ * limitations under the License. */ -package com.google.code.samples.oauth2; +package main.com.google.code.samples.oauth2; import java.io.IOException; -import java.net.URISyntaxException; import java.util.logging.Logger; import javax.security.auth.callback.Callback; import javax.security.auth.callback.CallbackHandler; import javax.security.auth.callback.NameCallback; -import javax.security.auth.callback.PasswordCallback; import javax.security.auth.callback.UnsupportedCallbackException; import javax.security.sasl.SaslClient; import javax.security.sasl.SaslException; +import org.apache.maven.surefire.shared.lang3.NotImplementedException; + /** * An OAuth2 implementation of SaslClient. @@ -39,7 +39,8 @@ class OAuth2SaslClient implements SaslClient { private final CallbackHandler callbackHandler; private boolean isComplete = false; - + private String MechanismName = "XOAUTH2"; + private String Email; /** * Creates a new instance of the OAuth2SaslClient. This will ordinarily only * be called from OAuth2SaslClientFactory. @@ -50,8 +51,14 @@ public OAuth2SaslClient(String oauthToken, this.callbackHandler = callbackHandler; } + public OAuth2SaslClient() + { + this.oauthToken = null; + this.callbackHandler = null; + } + public String getMechanismName() { - return "XOAUTH2"; + return this.MechanismName; } public boolean hasInitialResponse() { @@ -73,12 +80,12 @@ public byte[] evaluateChallenge(byte[] challenge) throws SaslException { } catch (IOException e) { throw new SaslException("Failed to execute callback: " + e); } - String email = nameCallback.getName(); + this.Email = nameCallback.getName(); - byte[] response = String.format("user=%s\1auth=Bearer %s\1\1", email, + byte[] postToAppendToServerAddress = String.format("user=%s\1auth=Bearer %s\1\1", this.Email, oauthToken).getBytes(); isComplete = true; - return response; + return postToAppendToServerAddress; } public boolean isComplete() { @@ -87,19 +94,43 @@ public boolean isComplete() { public byte[] unwrap(byte[] incoming, int offset, int len) throws SaslException { - throw new IllegalStateException(); + throw new NotImplementedException(); } public byte[] wrap(byte[] outgoing, int offset, int len) throws SaslException { - throw new IllegalStateException(); + throw new NotImplementedException(); } public Object getNegotiatedProperty(String propName) { if (!isComplete()) { throw new IllegalStateException(); } - return null; + switch(propName) + { + case "Email": + if(this.Email == null) + return null; + else + return this.Email; + case "MechanismName": + if(this.MechanismName == null) + return null; + else + return this.getMechanismName(); + case "OAuthToken": + if(null == this.oauthToken) + return null; + else + return this.oauthToken; + case "CallbackHandler": + if(this.callbackHandler == null) + return null; + else + return this.callbackHandler; + default: + return null; + } } public void dispose() throws SaslException { diff --git a/java/com/google/code/samples/oauth2/OAuth2SaslClientFactory.java b/java/src/main/com/google/code/samples/oauth2/OAuth2SaslClientFactory.java similarity index 97% rename from java/com/google/code/samples/oauth2/OAuth2SaslClientFactory.java rename to java/src/main/com/google/code/samples/oauth2/OAuth2SaslClientFactory.java index f399ad5..d39a974 100644 --- a/java/com/google/code/samples/oauth2/OAuth2SaslClientFactory.java +++ b/java/src/main/com/google/code/samples/oauth2/OAuth2SaslClientFactory.java @@ -13,7 +13,7 @@ * limitations under the License. */ -package com.google.code.samples.oauth2; +package main.com.google.code.samples.oauth2; import java.util.Map; @@ -56,7 +56,7 @@ public SaslClient createSaslClient(String[] mechanisms, return new OAuth2SaslClient((String) props.get(OAUTH_TOKEN_PROP), callbackHandler); } - + public String[] getMechanismNames(Map props) { return new String[] {"XOAUTH2"}; } diff --git a/java/src/test/com/google/code/samples/oauth2/OAuth2AuthenticatorTests.java b/java/src/test/com/google/code/samples/oauth2/OAuth2AuthenticatorTests.java new file mode 100644 index 0000000..bf6fd04 --- /dev/null +++ b/java/src/test/com/google/code/samples/oauth2/OAuth2AuthenticatorTests.java @@ -0,0 +1,34 @@ +package main.com.google.code.samples.oauth2; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.Assume.assumeTrue; +import static org.junit.Assume.assumeFalse; +import org.junit.jupiter.api.BeforeAll; + +import org.junit.jupiter.api.Test; + +import main.com.google.code.samples.oauth2.OAuth2Authenticator; + +import org.junit.jupiter.api.DisplayName; + +public class OAuth2AuthenticatorTests { + + private final OAuth2Authenticator oAuth2Authenticator = new OAuth2Authenticator(); + + @DisplayName("Assumptions needed for tests") + @Test + void trueAssumption() { + assumeTrue((4 % 1) == 0); + assertEquals(8 % 3, 2); + assumeTrue((7 % 5) == 2); + assertEquals(7 % 3, 1); + assumeTrue(((2 + 2) % 2) == 0); + assumeFalse(((2 + 2) % 2) == 1); + assertEquals(2 + 2, 4); + } + + @BeforeAll + static void initAll() { + + } +} \ No newline at end of file diff --git a/java/src/test/com/google/code/samples/oauth2/OAuth2SaslClientFactoryTests.java b/java/src/test/com/google/code/samples/oauth2/OAuth2SaslClientFactoryTests.java new file mode 100644 index 0000000..d1db0a4 --- /dev/null +++ b/java/src/test/com/google/code/samples/oauth2/OAuth2SaslClientFactoryTests.java @@ -0,0 +1,31 @@ +package main.pro.freemania.code.samples.oauth2; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.Assume.assumeTrue; + +import org.junit.jupiter.api.Test; + +import com.google.common.annotations.VisibleForTesting; + +import main.com.google.code.samples.oauth2.OAuth2SaslClientFactory; + +import org.junit.jupiter.api.BeforeAll; +import org.junit.jupiter.api.DisplayName; +import main.com.google.code.samples.oauth2.OAuth2SaslClient; + +public class OAuth2SaslClientFactoryTests { + + private final OAuth2SaslClientFactory oAuth2SaslClientFactory = new OAuth2SaslClientFactory(); + + @BeforeAll + static void initAll() { + + } + + @Test + void getMechanismNameXOAUTH() + { + OAuth2SaslClient oAuth2SaslClient = new OAuth2SaslClient(); + assertEquals(oAuth2SaslClient.getMechanismName(), oAuth2SaslClientFactory.getMechanismNames(null)[0]); + } +} \ No newline at end of file diff --git a/java/src/test/com/google/code/samples/oauth2/OAuth2SaslClientTests.java b/java/src/test/com/google/code/samples/oauth2/OAuth2SaslClientTests.java new file mode 100644 index 0000000..04343bc --- /dev/null +++ b/java/src/test/com/google/code/samples/oauth2/OAuth2SaslClientTests.java @@ -0,0 +1,55 @@ +package main.pro.freemania.code.samples.oauth2; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.Assume.assumeTrue; +import static org.junit.jupiter.api.Assertions.assertThrows; +import static org.junit.jupiter.api.Assertions.assertTrue; +import static org.mockito.Mockito.mock; +import static org.mockito.Mockito.when; + +import org.apache.maven.surefire.api.testset.TestSetFailedException; +import org.junit.jupiter.api.BeforeAll; +import org.junit.runner.RunWith; +import org.mockito.InjectMocks; +import org.mockito.Mock; +import org.junit.jupiter.api.Test; +import org.junit.jupiter.api.DisplayName; +import javax.security.auth.callback.CallbackHandler; +import java.lang.reflect.Field; +import main.com.google.code.samples.oauth2.OAuth2SaslClient; + +class OAuth2SaslClientTests { + + private final OAuth2SaslClient oAuth2SaslClient = new OAuth2SaslClient(); + + @Test + public void oAuth2SaslClientgetNegotiatedPropertyThrows() throws TestSetFailedException { + + String message = new String("propertyToSet"); + + assertThrows(IllegalStateException.class + , () -> { + oAuth2SaslClient.getNegotiatedProperty(message); + }); + } + + @Test + public void hasInitialResponseTest() + { + assertTrue(oAuth2SaslClient.hasInitialResponse()); + } + + public Field getPrivateFieldObject (String nameOfField) throws NoSuchFieldException + { + return OAuth2SaslClient.class.getDeclaredField(nameOfField); + } + + @Test + public void getNegotiatedPropertyMechanismName() throws IllegalAccessException, NoSuchFieldException + { + Field f = getPrivateFieldObject("MechanismName"); + f.setAccessible(true); + String name = (String)f.get(this.oAuth2SaslClient); + assertEquals(oAuth2SaslClient.getMechanismName(), name); + } +} \ No newline at end of file diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..f8522f8 --- /dev/null +++ b/pom.xml @@ -0,0 +1,114 @@ + + + 4.0.0 + com.google.code.samples.oauth2 + gmail-oauth2-tools + 1.0.1-SNAPSHOT + jar + https://github.com/FreedomFaighter/gmail-oauth2-tools + gmail-oauth2-tools + Tools and sample code for authenticating to Gmail with OAuth2. + + UTF-8 + UTF-8 + 11 + + + + + + org.junit.platform + junit-platform-runner + 1.10.0 + test + + + org.junit.jupiter + junit-jupiter-engine + 5.9.2 + test + + + javax.mail + mail + 1.4.7 + + + org.mockito + mockito-junit-jupiter + 5.4.0 + test + + + + org.apache.maven.plugins + maven-surefire-plugin + 3.1.2 + + + + java/src/main + java/src/test + ${project.basedir}/target + ${project.build.directory}/classes + ${project.artifactId}-${project.version} + ${project.build.directory}/test-classes + + + maven-compiler-plugin + + 11 + 11 + + + + maven-jar-plugin + 3.3.0 + + + default-jar + package + + jar + + + + + + + true + com.google.code.samples.oauth2.OAuth2Authenticator + + + + + + org.apache.maven.plugins + maven-surefire-plugin + 3.1.2 + + + **/*Tests.java + + + + + + org.junit.platform + junit-platform-surefire-provider + 1.3.2 + + + + org.apache.maven.surefire + surefire-api + 3.1.2 + + + + + + \ No newline at end of file diff --git a/python/oauth2.py b/python/oauth2.py index eb14632..43f7b8e 100755 --- a/python/oauth2.py +++ b/python/oauth2.py @@ -22,7 +22,8 @@ registering and for documentation of the APIs invoked by this code. NOTE: The OAuth2 OOB flow isn't a thing anymore. You will need to set the -application type to "Web application" and then add https://oauth2.dance/ as an +application type to "Web application" and then add +https://google.github.io/gmail-oauth2-tools/html/oauth2.dance.html as an authorised redirect URI. This is necessary for seeing the authorisation code on a page in your browser. @@ -134,7 +135,7 @@ def SetupOptionParser(): # Hardcoded redirect URI. -REDIRECT_URI = 'https://oauth2.dance/' +REDIRECT_URI = 'https://google.github.io/gmail-oauth2-tools/html/oauth2.dance.html' def AccountsUrl(command):