diff --git a/src/main/java/org/kohsuke/github/GHOTPRequiredException.java b/src/main/java/org/kohsuke/github/GHOTPRequiredException.java
new file mode 100644
index 000000000..f7d935bd5
--- /dev/null
+++ b/src/main/java/org/kohsuke/github/GHOTPRequiredException.java
@@ -0,0 +1,10 @@
+package org.kohsuke.github;
+/**
+ * This exception is thrown when GitHub is requesting an OTP from the user
+ *
+ * @author Kevin Harrington mad.hephaestus@gmail.com
+ *
+ */
+public class GHOTPRequiredException extends GHIOException {
+//...
+}
diff --git a/src/main/java/org/kohsuke/github/GitHub.java b/src/main/java/org/kohsuke/github/GitHub.java
index fcd4c171a..94ffd6f0b 100644
--- a/src/main/java/org/kohsuke/github/GitHub.java
+++ b/src/main/java/org/kohsuke/github/GitHub.java
@@ -44,6 +44,7 @@ import java.text.SimpleDateFormat;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
+import java.util.function.Supplier;
import java.util.logging.Logger;
import static com.fasterxml.jackson.annotation.JsonAutoDetect.Visibility.ANY;
@@ -702,7 +703,31 @@ public class GitHub {
return requester.method("POST").to("/authorizations", GHAuthorization.class).wrap(this);
}
-
+ /**
+ * Creates a new authorization using an OTP.
+ *
+ * Start by running createToken, if exception is thrown, prompt for OTP from user
+ *
+ * Once OTP is received, call this token request
+ *
+ * The token created can be then used for {@link GitHub#connectUsingOAuth(String)} in the future.
+ *
+ * @see Documentation
+ */
+ public GHAuthorization createToken(Collection scope, String note, String noteUrl, Supplier OTP) throws IOException{
+ try {
+ return createToken(scope, note, noteUrl);
+ }catch (GHOTPRequiredException ex){
+ String OTPstring=OTP.get();
+ Requester requester = new Requester(this)
+ .with("scopes", scope)
+ .with("note", note)
+ .with("note_url", noteUrl);
+ // Add the OTP from the user
+ requester.setHeader("x-github-otp", OTPstring);
+ return requester.method("POST").to("/authorizations", GHAuthorization.class).wrap(this);
+ }
+ }
/**
* @see docs
*/
diff --git a/src/main/java/org/kohsuke/github/Requester.java b/src/main/java/org/kohsuke/github/Requester.java
index 4078081cc..12765bd45 100644
--- a/src/main/java/org/kohsuke/github/Requester.java
+++ b/src/main/java/org/kohsuke/github/Requester.java
@@ -763,8 +763,13 @@ class Requester {
IOUtils.closeQuietly(es);
}
}
- if (responseCode == HttpURLConnection.HTTP_UNAUTHORIZED) // 401 / Unauthorized == bad creds
- throw e;
+ if (responseCode == HttpURLConnection.HTTP_UNAUTHORIZED) // 401 Unauthorized == bad creds or OTP request
+ // In the case of a user with 2fa enabled, a header with X-GitHub-OTP
+ // will be returned indicating the user needs to respond with an otp
+ if(uc.getHeaderField("X-GitHub-OTP") != null)
+ throw (IOException) new GHOTPRequiredException().withResponseHeaderFields(uc).initCause(e);
+ else
+ throw e; // usually org.kohsuke.github.HttpException (which extends IOException)
if ("0".equals(uc.getHeaderField("X-RateLimit-Remaining"))) {
root.rateLimitHandler.onError(e,uc);
diff --git a/src/test/java/org/kohsuke/github/Github2faTest.java b/src/test/java/org/kohsuke/github/Github2faTest.java
new file mode 100644
index 000000000..ebac9e684
--- /dev/null
+++ b/src/test/java/org/kohsuke/github/Github2faTest.java
@@ -0,0 +1,38 @@
+package org.kohsuke.github;
+
+import java.io.IOException;
+import java.util.Arrays;
+import java.util.List;
+
+import org.junit.Test;
+/**
+ * @author Kevin Harrington mad.hephaestus@gmail.com
+ */
+public class Github2faTest extends AbstractGitHubWireMockTest {
+
+ @Test
+ public void test2faToken() throws IOException {
+ assertFalse("Test only valid when not proxying", mockGitHub.isUseProxy());
+
+ List asList = Arrays.asList("repo", "gist", "write:packages", "read:packages", "delete:packages",
+ "user", "delete_repo");
+ String nameOfToken = "Test2faTokenCreate";//+timestamp;// use time stamp to ensure the token creations do not collide with older tokens
+
+ GHAuthorization token=gitHub.createToken(
+ asList,
+ nameOfToken,
+ "this is a test token created by a unit test", () -> {
+ String data = "111878";
+ // TO UPDATE run this in debugger mode, put a breakpoint here, and enter the OTP you get into the value of Data
+ return data;
+ });
+ assert token!=null;
+ for(int i=0;i