diff --git a/src/main/java/org/kohsuke/github/GitHubClient.java b/src/main/java/org/kohsuke/github/GitHubClient.java index 1d38e5fb63..535007d79d 100644 --- a/src/main/java/org/kohsuke/github/GitHubClient.java +++ b/src/main/java/org/kohsuke/github/GitHubClient.java @@ -148,13 +148,15 @@ private T fetch(Class type, String urlPath) throws IOException { } /** - * Ensures that the credential is valid. + * Ensures that the credential for this client is valid. * * @return the boolean */ public boolean isCredentialValid() { try { - fetch(GHUser.class, "/user"); + // If 404, ratelimit returns a default value. + // This works as credential test because invalid credentials returns 401, not 404 + getRateLimit(); return true; } catch (IOException e) { if (LOGGER.isLoggable(FINE)) diff --git a/src/test/java/org/kohsuke/github/AppTest.java b/src/test/java/org/kohsuke/github/AppTest.java index 5e6c31b75a..14826c9bed 100755 --- a/src/test/java/org/kohsuke/github/AppTest.java +++ b/src/test/java/org/kohsuke/github/AppTest.java @@ -77,8 +77,34 @@ private void cleanupUserRepository(final String name) throws IOException { @Test public void testCredentialValid() throws IOException { assertTrue(gitHub.isCredentialValid()); - GitHub connect = GitHub.connect("totally", "bogus"); - assertFalse(connect.isCredentialValid()); + assertThat(gitHub.lastRateLimit().getCore(), not(instanceOf(GHRateLimit.UnknownLimitRecord.class))); + assertThat(gitHub.lastRateLimit().getCore().getLimit(), equalTo(5000)); + + gitHub = getGitHubBuilder().withOAuthToken("bogus", "user") + .withEndpoint(mockGitHub.apiServer().baseUrl()) + .build(); + assertThat(gitHub.lastRateLimit(), nullValue()); + assertFalse(gitHub.isCredentialValid()); + // For invalid credentials, we get a 401 but it includes anonymous rate limit headers + assertThat(gitHub.lastRateLimit().getCore(), not(instanceOf(GHRateLimit.UnknownLimitRecord.class))); + assertThat(gitHub.lastRateLimit().getCore().getLimit(), equalTo(60)); + } + + @Test + public void testCredentialValidEnterprise() throws IOException { + // Simulated GHE: getRateLimit returns 404 + assertThat(gitHub.lastRateLimit(), nullValue()); + assertTrue(gitHub.isCredentialValid()); + // lastRateLimit stays null when 404 is encountered + assertThat(gitHub.lastRateLimit(), nullValue()); + + gitHub = getGitHubBuilder().withOAuthToken("bogus", "user") + .withEndpoint(mockGitHub.apiServer().baseUrl()) + .build(); + assertThat(gitHub.lastRateLimit(), nullValue()); + assertFalse(gitHub.isCredentialValid()); + // Simulated GHE: For invalid credentials, we get a 401 that does not include ratelimit info + assertThat(gitHub.lastRateLimit(), nullValue()); } @Test diff --git a/src/test/resources/org/kohsuke/github/AppTest/wiremock/testCredentialValid/__files/rate_limit-2.json b/src/test/resources/org/kohsuke/github/AppTest/wiremock/testCredentialValid/__files/rate_limit-2.json new file mode 100644 index 0000000000..6c2f11dbd3 --- /dev/null +++ b/src/test/resources/org/kohsuke/github/AppTest/wiremock/testCredentialValid/__files/rate_limit-2.json @@ -0,0 +1,34 @@ +{ + "resources": { + "core": { + "limit": 5000, + "remaining": 4994, + "reset": 1585671841 + }, + "search": { + "limit": 30, + "remaining": 30, + "reset": 1585671895 + }, + "graphql": { + "limit": 5000, + "remaining": 5000, + "reset": 1585675435 + }, + "integration_manifest": { + "limit": 5000, + "remaining": 5000, + "reset": 1585675435 + }, + "source_import": { + "limit": 100, + "remaining": 100, + "reset": 1585671895 + } + }, + "rate": { + "limit": 5000, + "remaining": 4994, + "reset": 1585671841 + } +} \ No newline at end of file diff --git a/src/test/resources/org/kohsuke/github/AppTest/wiremock/testCredentialValid/__files/user-1.json b/src/test/resources/org/kohsuke/github/AppTest/wiremock/testCredentialValid/__files/user-1.json index 39d6c8353e..08a55091ae 100644 --- a/src/test/resources/org/kohsuke/github/AppTest/wiremock/testCredentialValid/__files/user-1.json +++ b/src/test/resources/org/kohsuke/github/AppTest/wiremock/testCredentialValid/__files/user-1.json @@ -24,14 +24,14 @@ "email": "bitwiseman@gmail.com", "hireable": null, "bio": "https://twitter.com/bitwiseman", - "public_repos": 167, - "public_gists": 4, - "followers": 136, + "public_repos": 181, + "public_gists": 7, + "followers": 151, "following": 9, "created_at": "2012-07-11T20:38:33Z", - "updated_at": "2019-09-24T19:32:29Z", - "private_gists": 7, - "total_private_repos": 9, + "updated_at": "2020-03-27T19:14:56Z", + "private_gists": 8, + "total_private_repos": 10, "owned_private_repos": 0, "disk_usage": 33697, "collaborators": 0, diff --git a/src/test/resources/org/kohsuke/github/AppTest/wiremock/testCredentialValid/mappings/rate_limit-2.json b/src/test/resources/org/kohsuke/github/AppTest/wiremock/testCredentialValid/mappings/rate_limit-2.json new file mode 100644 index 0000000000..6be3487bf9 --- /dev/null +++ b/src/test/resources/org/kohsuke/github/AppTest/wiremock/testCredentialValid/mappings/rate_limit-2.json @@ -0,0 +1,44 @@ +{ + "id": "c875f18e-a0af-4114-8d1f-123b5377a52c", + "name": "rate_limit", + "request": { + "url": "/rate_limit", + "method": "GET", + "headers": { + "Accept": { + "equalTo": "text/html, image/gif, image/jpeg, *; q=.2, */*; q=.2" + } + } + }, + "response": { + "status": 200, + "bodyFileName": "rate_limit-2.json", + "headers": { + "Date": "Tue, 31 Mar 2020 16:23:55 GMT", + "Content-Type": "application/json; charset=utf-8", + "Server": "GitHub.com", + "Status": "200 OK", + "X-RateLimit-Limit": "5000", + "X-RateLimit-Remaining": "4994", + "X-RateLimit-Reset": "1585671841", + "Cache-Control": "no-cache", + "X-OAuth-Scopes": "admin:org, admin:org_hook, admin:public_key, admin:repo_hook, delete_repo, gist, notifications, repo, user, write:discussion", + "X-Accepted-OAuth-Scopes": "", + "X-GitHub-Media-Type": "unknown, github.v3", + "Strict-Transport-Security": "max-age=31536000; includeSubdomains; preload", + "X-Frame-Options": "deny", + "X-Content-Type-Options": "nosniff", + "X-XSS-Protection": "1; mode=block", + "Referrer-Policy": "origin-when-cross-origin, strict-origin-when-cross-origin", + "Content-Security-Policy": "default-src 'none'", + "Vary": "Accept-Encoding, Accept, X-Requested-With", + "X-GitHub-Request-Id": "C14E:8628:147DC:18E26:5E836E9A" + } + }, + "uuid": "c875f18e-a0af-4114-8d1f-123b5377a52c", + "persistent": true, + "scenarioName": "scenario-1-rate_limit", + "requiredScenarioState": "Started", + "newScenarioState": "scenario-1-rate_limit-2", + "insertionIndex": 2 +} \ No newline at end of file diff --git a/src/test/resources/org/kohsuke/github/AppTest/wiremock/testCredentialValid/mappings/rate_limit-3.json b/src/test/resources/org/kohsuke/github/AppTest/wiremock/testCredentialValid/mappings/rate_limit-3.json new file mode 100644 index 0000000000..cc3e260dce --- /dev/null +++ b/src/test/resources/org/kohsuke/github/AppTest/wiremock/testCredentialValid/mappings/rate_limit-3.json @@ -0,0 +1,40 @@ +{ + "id": "5ba21199-9f1d-420c-87e9-ab30d3e73884", + "name": "rate_limit", + "request": { + "url": "/rate_limit", + "method": "GET", + "headers": { + "Accept": { + "equalTo": "text/html, image/gif, image/jpeg, *; q=.2, */*; q=.2" + } + } + }, + "response": { + "status": 401, + "body": "{\"message\":\"Bad credentials\",\"documentation_url\":\"https://developer.github.com/v3\"}", + "headers": { + "Date": "Tue, 31 Mar 2020 16:23:55 GMT", + "Content-Type": "application/json; charset=utf-8", + "Server": "GitHub.com", + "Status": "401 Unauthorized", + "X-GitHub-Media-Type": "unknown, github.v3", + "X-RateLimit-Limit": "60", + "X-RateLimit-Remaining": "59", + "X-RateLimit-Reset": "1585675435", + "Strict-Transport-Security": "max-age=31536000; includeSubdomains; preload", + "X-Frame-Options": "deny", + "X-Content-Type-Options": "nosniff", + "X-XSS-Protection": "1; mode=block", + "Referrer-Policy": "origin-when-cross-origin, strict-origin-when-cross-origin", + "Content-Security-Policy": "default-src 'none'", + "Vary": "Accept-Encoding, Accept, X-Requested-With", + "X-GitHub-Request-Id": "C14E:8628:147E6:18E5B:5E836E9B" + } + }, + "uuid": "5ba21199-9f1d-420c-87e9-ab30d3e73884", + "persistent": true, + "scenarioName": "scenario-1-rate_limit", + "requiredScenarioState": "scenario-1-rate_limit-2", + "insertionIndex": 3 +} \ No newline at end of file diff --git a/src/test/resources/org/kohsuke/github/AppTest/wiremock/testCredentialValid/mappings/user-1.json b/src/test/resources/org/kohsuke/github/AppTest/wiremock/testCredentialValid/mappings/user-1.json index b64c18404e..1db9b964cb 100644 --- a/src/test/resources/org/kohsuke/github/AppTest/wiremock/testCredentialValid/mappings/user-1.json +++ b/src/test/resources/org/kohsuke/github/AppTest/wiremock/testCredentialValid/mappings/user-1.json @@ -1,5 +1,5 @@ { - "id": "eba96b24-3354-4d21-bd2a-388a13ba9832", + "id": "27891e8f-aae7-459b-9860-a4f505cec688", "name": "user", "request": { "url": "/user", @@ -14,35 +14,33 @@ "status": 200, "bodyFileName": "user-1.json", "headers": { - "Date": "Wed, 02 Oct 2019 21:39:57 GMT", + "Date": "Tue, 31 Mar 2020 16:23:54 GMT", "Content-Type": "application/json; charset=utf-8", "Server": "GitHub.com", "Status": "200 OK", "X-RateLimit-Limit": "5000", - "X-RateLimit-Remaining": "4964", - "X-RateLimit-Reset": "1570055937", + "X-RateLimit-Remaining": "4995", + "X-RateLimit-Reset": "1585671841", "Cache-Control": "private, max-age=60, s-maxage=60", "Vary": [ "Accept, Authorization, Cookie, X-GitHub-OTP", - "Accept-Encoding" + "Accept-Encoding, Accept, X-Requested-With" ], - "ETag": "W/\"cf6199fecf47b59c42190e1e11147ee2\"", - "Last-Modified": "Tue, 24 Sep 2019 19:32:29 GMT", + "ETag": "W/\"740bb7db37d5437d08ea1aa5e652cf37\"", + "Last-Modified": "Fri, 27 Mar 2020 19:14:56 GMT", "X-OAuth-Scopes": "admin:org, admin:org_hook, admin:public_key, admin:repo_hook, delete_repo, gist, notifications, repo, user, write:discussion", "X-Accepted-OAuth-Scopes": "", "X-GitHub-Media-Type": "unknown, github.v3", - "Access-Control-Expose-Headers": "ETag, Link, Location, Retry-After, X-GitHub-OTP, X-RateLimit-Limit, X-RateLimit-Remaining, X-RateLimit-Reset, X-OAuth-Scopes, X-Accepted-OAuth-Scopes, X-Poll-Interval, X-GitHub-Media-Type", - "Access-Control-Allow-Origin": "*", "Strict-Transport-Security": "max-age=31536000; includeSubdomains; preload", "X-Frame-Options": "deny", "X-Content-Type-Options": "nosniff", "X-XSS-Protection": "1; mode=block", "Referrer-Policy": "origin-when-cross-origin, strict-origin-when-cross-origin", "Content-Security-Policy": "default-src 'none'", - "X-GitHub-Request-Id": "C2BA:67D6:CA2979:EFEEDC:5D95192D" + "X-GitHub-Request-Id": "C14E:8628:147B0:18E1A:5E836E9A" } }, - "uuid": "eba96b24-3354-4d21-bd2a-388a13ba9832", + "uuid": "27891e8f-aae7-459b-9860-a4f505cec688", "persistent": true, "insertionIndex": 1 } \ No newline at end of file diff --git a/src/test/resources/org/kohsuke/github/AppTest/wiremock/testCredentialValidEnterprise/__files/user-1.json b/src/test/resources/org/kohsuke/github/AppTest/wiremock/testCredentialValidEnterprise/__files/user-1.json new file mode 100644 index 0000000000..08a55091ae --- /dev/null +++ b/src/test/resources/org/kohsuke/github/AppTest/wiremock/testCredentialValidEnterprise/__files/user-1.json @@ -0,0 +1,45 @@ +{ + "login": "bitwiseman", + "id": 1958953, + "node_id": "MDQ6VXNlcjE5NTg5NTM=", + "avatar_url": "https://avatars3.githubusercontent.com/u/1958953?v=4", + "gravatar_id": "", + "url": "https://api.github.com/users/bitwiseman", + "html_url": "https://github.com/bitwiseman", + "followers_url": "https://api.github.com/users/bitwiseman/followers", + "following_url": "https://api.github.com/users/bitwiseman/following{/other_user}", + "gists_url": "https://api.github.com/users/bitwiseman/gists{/gist_id}", + "starred_url": "https://api.github.com/users/bitwiseman/starred{/owner}{/repo}", + "subscriptions_url": "https://api.github.com/users/bitwiseman/subscriptions", + "organizations_url": "https://api.github.com/users/bitwiseman/orgs", + "repos_url": "https://api.github.com/users/bitwiseman/repos", + "events_url": "https://api.github.com/users/bitwiseman/events{/privacy}", + "received_events_url": "https://api.github.com/users/bitwiseman/received_events", + "type": "User", + "site_admin": false, + "name": "Liam Newman", + "company": "Cloudbees, Inc.", + "blog": "", + "location": "Seattle, WA, USA", + "email": "bitwiseman@gmail.com", + "hireable": null, + "bio": "https://twitter.com/bitwiseman", + "public_repos": 181, + "public_gists": 7, + "followers": 151, + "following": 9, + "created_at": "2012-07-11T20:38:33Z", + "updated_at": "2020-03-27T19:14:56Z", + "private_gists": 8, + "total_private_repos": 10, + "owned_private_repos": 0, + "disk_usage": 33697, + "collaborators": 0, + "two_factor_authentication": true, + "plan": { + "name": "free", + "space": 976562499, + "collaborators": 0, + "private_repos": 10000 + } +} \ No newline at end of file diff --git a/src/test/resources/org/kohsuke/github/AppTest/wiremock/testCredentialValidEnterprise/mappings/rate_limit-2.json b/src/test/resources/org/kohsuke/github/AppTest/wiremock/testCredentialValidEnterprise/mappings/rate_limit-2.json new file mode 100644 index 0000000000..cdef957c18 --- /dev/null +++ b/src/test/resources/org/kohsuke/github/AppTest/wiremock/testCredentialValidEnterprise/mappings/rate_limit-2.json @@ -0,0 +1,41 @@ +{ + "id": "c875f18e-a0af-4114-8d1f-123b5377a52c", + "name": "rate_limit", + "request": { + "url": "/rate_limit", + "method": "GET", + "headers": { + "Accept": { + "equalTo": "text/html, image/gif, image/jpeg, *; q=.2, */*; q=.2" + } + } + }, + "response": { + "status": 404, + "body": "{\"message\":\"Not Found\",\"documentation_url\":\"https://developer.github.com/v3\"}", + "headers": { + "Date": "Tue, 31 Mar 2020 16:23:55 GMT", + "Content-Type": "application/json; charset=utf-8", + "Server": "GitHub.com", + "Status": "404 Not Found", + "Cache-Control": "no-cache", + "X-OAuth-Scopes": "admin:org, admin:org_hook, admin:public_key, admin:repo_hook, delete_repo, gist, notifications, repo, user, write:discussion", + "X-Accepted-OAuth-Scopes": "", + "X-GitHub-Media-Type": "unknown, github.v3", + "Strict-Transport-Security": "max-age=31536000; includeSubdomains; preload", + "X-Frame-Options": "deny", + "X-Content-Type-Options": "nosniff", + "X-XSS-Protection": "1; mode=block", + "Referrer-Policy": "origin-when-cross-origin, strict-origin-when-cross-origin", + "Content-Security-Policy": "default-src 'none'", + "Vary": "Accept-Encoding, Accept, X-Requested-With", + "X-GitHub-Request-Id": "C14E:8628:147DC:18E26:5E836E9A" + } + }, + "uuid": "c875f18e-a0af-4114-8d1f-123b5377a52c", + "persistent": true, + "scenarioName": "scenario-1-rate_limit", + "requiredScenarioState": "Started", + "newScenarioState": "scenario-1-rate_limit-2", + "insertionIndex": 2 +} \ No newline at end of file diff --git a/src/test/resources/org/kohsuke/github/AppTest/wiremock/testCredentialValidEnterprise/mappings/rate_limit-3.json b/src/test/resources/org/kohsuke/github/AppTest/wiremock/testCredentialValidEnterprise/mappings/rate_limit-3.json new file mode 100644 index 0000000000..f5d6cbfe7f --- /dev/null +++ b/src/test/resources/org/kohsuke/github/AppTest/wiremock/testCredentialValidEnterprise/mappings/rate_limit-3.json @@ -0,0 +1,37 @@ +{ + "id": "5ba21199-9f1d-420c-87e9-ab30d3e73884", + "name": "rate_limit", + "request": { + "url": "/rate_limit", + "method": "GET", + "headers": { + "Accept": { + "equalTo": "text/html, image/gif, image/jpeg, *; q=.2, */*; q=.2" + } + } + }, + "response": { + "status": 401, + "body": "{\"message\":\"Bad credentials\",\"documentation_url\":\"https://developer.github.com/v3\"}", + "headers": { + "Date": "Tue, 31 Mar 2020 16:23:55 GMT", + "Content-Type": "application/json; charset=utf-8", + "Server": "GitHub.com", + "Status": "401 Unauthorized", + "X-GitHub-Media-Type": "unknown, github.v3", + "Strict-Transport-Security": "max-age=31536000; includeSubdomains; preload", + "X-Frame-Options": "deny", + "X-Content-Type-Options": "nosniff", + "X-XSS-Protection": "1; mode=block", + "Referrer-Policy": "origin-when-cross-origin, strict-origin-when-cross-origin", + "Content-Security-Policy": "default-src 'none'", + "Vary": "Accept-Encoding, Accept, X-Requested-With", + "X-GitHub-Request-Id": "C14E:8628:147E6:18E5B:5E836E9B" + } + }, + "uuid": "5ba21199-9f1d-420c-87e9-ab30d3e73884", + "persistent": true, + "scenarioName": "scenario-1-rate_limit", + "requiredScenarioState": "scenario-1-rate_limit-2", + "insertionIndex": 3 +} \ No newline at end of file diff --git a/src/test/resources/org/kohsuke/github/AppTest/wiremock/testCredentialValidEnterprise/mappings/user-1.json b/src/test/resources/org/kohsuke/github/AppTest/wiremock/testCredentialValidEnterprise/mappings/user-1.json new file mode 100644 index 0000000000..d53a2bcff2 --- /dev/null +++ b/src/test/resources/org/kohsuke/github/AppTest/wiremock/testCredentialValidEnterprise/mappings/user-1.json @@ -0,0 +1,43 @@ +{ + "id": "27891e8f-aae7-459b-9860-a4f505cec688", + "name": "user", + "request": { + "url": "/user", + "method": "GET", + "headers": { + "Accept": { + "equalTo": "text/html, image/gif, image/jpeg, *; q=.2, */*; q=.2" + } + } + }, + "response": { + "status": 200, + "bodyFileName": "user-1.json", + "headers": { + "Date": "Tue, 31 Mar 2020 16:23:54 GMT", + "Content-Type": "application/json; charset=utf-8", + "Server": "GitHub.com", + "Status": "200 OK", + "Cache-Control": "private, max-age=60, s-maxage=60", + "Vary": [ + "Accept, Authorization, Cookie, X-GitHub-OTP", + "Accept-Encoding, Accept, X-Requested-With" + ], + "ETag": "W/\"740bb7db37d5437d08ea1aa5e652cf37\"", + "Last-Modified": "Fri, 27 Mar 2020 19:14:56 GMT", + "X-OAuth-Scopes": "admin:org, admin:org_hook, admin:public_key, admin:repo_hook, delete_repo, gist, notifications, repo, user, write:discussion", + "X-Accepted-OAuth-Scopes": "", + "X-GitHub-Media-Type": "unknown, github.v3", + "Strict-Transport-Security": "max-age=31536000; includeSubdomains; preload", + "X-Frame-Options": "deny", + "X-Content-Type-Options": "nosniff", + "X-XSS-Protection": "1; mode=block", + "Referrer-Policy": "origin-when-cross-origin, strict-origin-when-cross-origin", + "Content-Security-Policy": "default-src 'none'", + "X-GitHub-Request-Id": "C14E:8628:147B0:18E1A:5E836E9A" + } + }, + "uuid": "27891e8f-aae7-459b-9860-a4f505cec688", + "persistent": true, + "insertionIndex": 1 +} \ No newline at end of file