GitHub.java
/*
* The MIT License
*
* Copyright (c) 2010, Kohsuke Kawaguchi
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in
* all copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
* THE SOFTWARE.
*/
package org.kohsuke.github;
import com.fasterxml.jackson.databind.ObjectReader;
import com.fasterxml.jackson.databind.ObjectWriter;
import edu.umd.cs.findbugs.annotations.SuppressFBWarnings;
import org.kohsuke.github.authorization.AuthorizationProvider;
import org.kohsuke.github.authorization.ImmutableAuthorizationProvider;
import org.kohsuke.github.authorization.UserAuthorizationProvider;
import org.kohsuke.github.connector.GitHubConnector;
import java.io.*;
import java.util.*;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
import java.util.function.Supplier;
import java.util.logging.Logger;
import javax.annotation.CheckForNull;
import javax.annotation.Nonnull;
// TODO: Auto-generated Javadoc
/**
* Root of the GitHub API.
*
* <h2>Thread safety</h2>
* <p>
* This library aims to be safe for use by multiple threads concurrently, although the library itself makes no attempt
* to control/serialize potentially conflicting operations to GitHub, such as updating & deleting a repository at
* the same time.
*
* @author Kohsuke Kawaguchi
*/
public class GitHub {
@Nonnull
private final GitHubClient client;
@CheckForNull
private GHMyself myself;
private final ConcurrentMap<String, GHUser> users;
private final ConcurrentMap<String, GHOrganization> orgs;
@Nonnull
private final GitHubSanityCachedValue<GHMeta> sanityCachedMeta = new GitHubSanityCachedValue<>();
/**
* Creates a client API root object.
*
* <p>
* Several different combinations of the login/oauthAccessToken/password parameters are allowed to represent
* different ways of authentication.
*
* <dl>
* <dt>Log in anonymously
* <dd>Leave all three parameters null and you will be making HTTP requests without any authentication.
*
* <dt>Log in with password
* <dd>Specify the login and password, then leave oauthAccessToken null. This will use the HTTP BASIC auth with the
* GitHub API.
*
* <dt>Log in with OAuth token
* <dd>Specify oauthAccessToken, and optionally specify the login. Leave password null. This will send OAuth token
* to the GitHub API. If the login parameter is null, The constructor makes an API call to figure out the user name
* that owns the token.
*
* <dt>Log in with JWT token
* <dd>Specify jwtToken. Leave password null. This will send JWT token to the GitHub API via the Authorization HTTP
* header. Please note that only operations in which permissions have been previously configured and accepted during
* the GitHub App will be executed successfully.
* </dl>
*
* @param apiUrl
* The URL of GitHub (or GitHub enterprise) API endpoint, such as "https://api.github.com" or
* "http://ghe.acme.com/api/v3". Note that GitHub Enterprise has <code>/api/v3</code> in the URL. For
* historical reasons, this parameter still accepts the bare domain name, but that's considered
* deprecated.
* @param connector
* a connector
* @param rateLimitHandler
* rateLimitHandler
* @param abuseLimitHandler
* abuseLimitHandler
* @param rateLimitChecker
* rateLimitChecker
* @param authorizationProvider
* a authorization provider
* @throws IOException
* Signals that an I/O exception has occurred.
*/
@SuppressFBWarnings(value = { "CT_CONSTRUCTOR_THROW" }, justification = "internal constructor")
GitHub(String apiUrl,
GitHubConnector connector,
GitHubRateLimitHandler rateLimitHandler,
GitHubAbuseLimitHandler abuseLimitHandler,
GitHubRateLimitChecker rateLimitChecker,
AuthorizationProvider authorizationProvider) throws IOException {
if (authorizationProvider instanceof DependentAuthorizationProvider) {
((DependentAuthorizationProvider) authorizationProvider).bind(this);
} else if (authorizationProvider instanceof ImmutableAuthorizationProvider
&& authorizationProvider instanceof UserAuthorizationProvider) {
UserAuthorizationProvider provider = (UserAuthorizationProvider) authorizationProvider;
if (provider.getLogin() == null && provider.getEncodedAuthorization() != null
&& provider.getEncodedAuthorization().startsWith("token")) {
authorizationProvider = new LoginLoadingUserAuthorizationProvider(provider, this);
}
}
users = new ConcurrentHashMap<>();
orgs = new ConcurrentHashMap<>();
this.client = new GitHubClient(apiUrl,
connector,
rateLimitHandler,
abuseLimitHandler,
rateLimitChecker,
authorizationProvider);
// Ensure we have the login if it is available
// This preserves previously existing behavior. Consider removing in future.
if (authorizationProvider instanceof LoginLoadingUserAuthorizationProvider) {
((LoginLoadingUserAuthorizationProvider) authorizationProvider).getLogin();
}
}
private GitHub(GitHubClient client) {
users = new ConcurrentHashMap<>();
orgs = new ConcurrentHashMap<>();
this.client = client;
}
private static class LoginLoadingUserAuthorizationProvider implements UserAuthorizationProvider {
private final GitHub gitHub;
private final AuthorizationProvider authorizationProvider;
private boolean loginLoaded = false;
private String login;
LoginLoadingUserAuthorizationProvider(AuthorizationProvider authorizationProvider, GitHub gitHub) {
this.gitHub = gitHub;
this.authorizationProvider = authorizationProvider;
}
@Override
public String getEncodedAuthorization() throws IOException {
return authorizationProvider.getEncodedAuthorization();
}
@Override
public String getLogin() {
synchronized (this) {
if (!loginLoaded) {
loginLoaded = true;
try {
GHMyself u = gitHub.setMyself();
if (u != null) {
login = u.getLogin();
}
} catch (IOException e) {
}
}
return login;
}
}
}
/**
* The Class DependentAuthorizationProvider.
*/
public static abstract class DependentAuthorizationProvider implements AuthorizationProvider {
private GitHub baseGitHub;
private GitHub gitHub;
private final AuthorizationProvider authorizationProvider;
/**
* An AuthorizationProvider that requires an authenticated GitHub instance to provide its authorization.
*
* @param authorizationProvider
* A authorization provider to be used when refreshing this authorization provider.
*/
@BetaApi
protected DependentAuthorizationProvider(AuthorizationProvider authorizationProvider) {
this.authorizationProvider = authorizationProvider;
}
/**
* Binds this authorization provider to a github instance.
*
* Only needs to be implemented by dynamic credentials providers that use a github instance in order to refresh.
*
* @param github
* The github instance to be used for refreshing dynamic credentials
*/
synchronized void bind(GitHub github) {
if (baseGitHub != null) {
throw new IllegalStateException("Already bound to another GitHub instance.");
}
this.baseGitHub = github;
}
/**
* Git hub.
*
* @return the git hub
*/
protected synchronized final GitHub gitHub() {
if (gitHub == null) {
gitHub = new GitHub.AuthorizationRefreshGitHubWrapper(this.baseGitHub, authorizationProvider);
}
return gitHub;
}
}
private static class AuthorizationRefreshGitHubWrapper extends GitHub {
private final AuthorizationProvider authorizationProvider;
AuthorizationRefreshGitHubWrapper(GitHub github, AuthorizationProvider authorizationProvider) {
super(github.client);
this.authorizationProvider = authorizationProvider;
// no dependent authorization providers nest like this currently, but they might in future
if (authorizationProvider instanceof DependentAuthorizationProvider) {
((DependentAuthorizationProvider) authorizationProvider).bind(this);
}
}
@Nonnull
@Override
Requester createRequest() {
try {
// Override
return super.createRequest().setHeader("Authorization", authorizationProvider.getEncodedAuthorization())
.rateLimit(RateLimitTarget.NONE);
} catch (IOException e) {
throw new GHException("Failed to create requester to refresh credentials", e);
}
}
}
/**
* Obtains the credential from "~/.github" or from the System Environment Properties.
*
* @return the git hub
* @throws IOException
* the io exception
*/
public static GitHub connect() throws IOException {
return GitHubBuilder.fromCredentials().build();
}
/**
* Version that connects to GitHub Enterprise.
*
* @param apiUrl
* The URL of GitHub (or GitHub Enterprise) API endpoint, such as "https://api.github.com" or
* "http://ghe.acme.com/api/v3". Note that GitHub Enterprise has <code>/api/v3</code> in the URL. For
* historical reasons, this parameter still accepts the bare domain name, but that's considered
* deprecated.
* @param login
* the login
* @param oauthAccessToken
* the oauth access token
* @return the git hub
* @throws IOException
* the io exception
*/
public static GitHub connectToEnterpriseWithOAuth(String apiUrl, String login, String oauthAccessToken)
throws IOException {
return new GitHubBuilder().withEndpoint(apiUrl).withOAuthToken(oauthAccessToken, login).build();
}
/**
* Connect git hub.
*
* @param login
* the login
* @param oauthAccessToken
* the oauth access token
* @return the git hub
* @throws IOException
* the io exception
*/
public static GitHub connect(String login, String oauthAccessToken) throws IOException {
return new GitHubBuilder().withOAuthToken(oauthAccessToken, login).build();
}
/**
* Connect using o auth git hub.
*
* @param oauthAccessToken
* the oauth access token
* @return the git hub
* @throws IOException
* the io exception
*/
public static GitHub connectUsingOAuth(String oauthAccessToken) throws IOException {
return new GitHubBuilder().withOAuthToken(oauthAccessToken).build();
}
/**
* Connect using o auth git hub.
*
* @param githubServer
* the github server
* @param oauthAccessToken
* the oauth access token
* @return the git hub
* @throws IOException
* the io exception
*/
public static GitHub connectUsingOAuth(String githubServer, String oauthAccessToken) throws IOException {
return new GitHubBuilder().withEndpoint(githubServer).withOAuthToken(oauthAccessToken).build();
}
/**
* Connects to GitHub anonymously.
* <p>
* All operations that require authentication will fail.
*
* @return the git hub
* @throws IOException
* the io exception
*/
public static GitHub connectAnonymously() throws IOException {
return new GitHubBuilder().build();
}
/**
* Connects to GitHub Enterprise anonymously.
* <p>
* All operations that require authentication will fail.
*
* @param apiUrl
* the api url
* @return the git hub
* @throws IOException
* the io exception
*/
public static GitHub connectToEnterpriseAnonymously(String apiUrl) throws IOException {
return new GitHubBuilder().withEndpoint(apiUrl).build();
}
/**
* An offline-only {@link GitHub} useful for parsing event notification from an unknown source.
* <p>
* All operations that require a connection will fail.
*
* @return An offline-only {@link GitHub}.
*/
public static GitHub offline() {
try {
return new GitHubBuilder().withEndpoint("https://api.github.invalid")
.withConnector(GitHubConnector.OFFLINE)
.build();
} catch (IOException e) {
throw new IllegalStateException("The offline implementation constructor should not connect", e);
}
}
/**
* Is this an anonymous connection.
*
* @return {@code true} if operations that require authentication will fail.
*/
public boolean isAnonymous() {
return client.isAnonymous();
}
/**
* Is this an always offline "connection".
*
* @return {@code true} if this is an always offline "connection".
*/
public boolean isOffline() {
return client.isOffline();
}
/**
* Gets api url.
*
* @return the api url
*/
public String getApiUrl() {
return client.getApiUrl();
}
/**
* Gets the current full rate limit information from the server.
*
* For some versions of GitHub Enterprise, the {@code /rate_limit} endpoint returns a {@code 404 Not Found}. In that
* case, the most recent {@link GHRateLimit} information will be returned, including rate limit information returned
* in the response header for this request in if was present.
*
* For most use cases it would be better to implement a {@link RateLimitChecker} and add it via
* {@link GitHubBuilder#withRateLimitChecker(RateLimitChecker)}.
*
* @return the rate limit
* @throws IOException
* the io exception
*/
@Nonnull
public GHRateLimit getRateLimit() throws IOException {
return client.getRateLimit();
}
/**
* Returns the most recently observed rate limit data or {@code null} if either there is no rate limit (for example
* GitHub Enterprise) or if no requests have been made.
*
* @return the most recently observed rate limit data or {@code null}.
* @deprecated implement a {@link RateLimitChecker} and add it via
* {@link GitHubBuilder#withRateLimitChecker(RateLimitChecker)}.
*/
@Nonnull
@Deprecated
public GHRateLimit lastRateLimit() {
return client.lastRateLimit();
}
/**
* Gets the current rate limit while trying not to actually make any remote requests unless absolutely necessary.
*
* @return the current rate limit data.
* @throws IOException
* if we couldn't get the current rate limit data.
* @deprecated implement a {@link RateLimitChecker} and add it via
* {@link GitHubBuilder#withRateLimitChecker(RateLimitChecker)}.
*/
@Nonnull
@Deprecated
public GHRateLimit rateLimit() throws IOException {
return client.rateLimit(RateLimitTarget.CORE);
}
/**
* Gets the {@link GHUser} that represents yourself.
*
* @return the myself
* @throws IOException
* the io exception
*/
@SuppressFBWarnings(value = { "EI_EXPOSE_REP" }, justification = "Expected")
public GHMyself getMyself() throws IOException {
client.requireCredential();
return setMyself();
}
private GHMyself setMyself() throws IOException {
synchronized (this) {
if (this.myself == null) {
this.myself = createRequest().withUrlPath("/user").fetch(GHMyself.class);
}
return myself;
}
}
/**
* Obtains the object that represents the named user.
*
* @param login
* the login
* @return the user
* @throws IOException
* the io exception
*/
public GHUser getUser(String login) throws IOException {
GHUser u = users.get(login);
if (u == null) {
u = createRequest().withUrlPath("/users/" + login).fetch(GHUser.class);
users.put(u.getLogin(), u);
}
return u;
}
/**
* clears all cached data in order for external changes (modifications and del) to be reflected.
*/
public void refreshCache() {
users.clear();
orgs.clear();
}
/**
* Interns the given {@link GHUser}.
*
* @param orig
* the orig
* @return the user
*/
protected GHUser getUser(GHUser orig) {
GHUser u = users.get(orig.getLogin());
if (u == null) {
users.put(orig.getLogin(), orig);
return orig;
}
return u;
}
/**
* Gets {@link GHOrganization} specified by name.
*
* @param name
* the name
* @return the organization
* @throws IOException
* the io exception
*/
public GHOrganization getOrganization(String name) throws IOException {
GHOrganization o = orgs.get(name);
if (o == null) {
o = createRequest().withUrlPath("/orgs/" + name).fetch(GHOrganization.class);
orgs.put(name, o);
}
return o;
}
/**
* Gets a list of all organizations.
*
* @return the paged iterable
*/
public PagedIterable<GHOrganization> listOrganizations() {
return listOrganizations(null);
}
/**
* Gets a list of all organizations starting after the organization identifier specified by 'since'.
*
* @param since
* the since
* @return the paged iterable
* @see <a href="https://developer.github.com/v3/orgs/#parameters">List All Orgs - Parameters</a>
*/
public PagedIterable<GHOrganization> listOrganizations(final String since) {
return createRequest().with("since", since)
.withUrlPath("/organizations")
.toIterable(GHOrganization[].class, null);
}
/**
* Gets the repository object from 'owner/repo' string that GitHub calls as "repository name".
*
* @param name
* the name
* @return the repository
* @throws IOException
* the io exception
* @see GHRepository#getName() GHRepository#getName()
*/
public GHRepository getRepository(String name) throws IOException {
String[] tokens = name.split("/");
if (tokens.length != 2) {
throw new IllegalArgumentException("Repository name must be in format owner/repo");
}
return GHRepository.read(this, tokens[0], tokens[1]);
}
/**
* Gets the repository object from its ID.
*
* @param id
* the id
* @return the repository by id
* @throws IOException
* the io exception
*/
public GHRepository getRepositoryById(long id) throws IOException {
return createRequest().withUrlPath("/repositories/" + id).fetch(GHRepository.class);
}
/**
* Returns a list of popular open source licenses.
*
* @return a list of popular open source licenses
* @throws IOException
* the io exception
* @see <a href="https://developer.github.com/v3/licenses/">GitHub API - Licenses</a>
*/
public PagedIterable<GHLicense> listLicenses() throws IOException {
return createRequest().withUrlPath("/licenses").toIterable(GHLicense[].class, null);
}
/**
* Returns a list of all users.
*
* @return the paged iterable
* @throws IOException
* the io exception
*/
public PagedIterable<GHUser> listUsers() throws IOException {
return createRequest().withUrlPath("/users").toIterable(GHUser[].class, null);
}
/**
* Returns the full details for a license.
*
* @param key
* The license key provided from the API
* @return The license details
* @throws IOException
* the io exception
* @see GHLicense#getKey() GHLicense#getKey()
*/
public GHLicense getLicense(String key) throws IOException {
return createRequest().withUrlPath("/licenses/" + key).fetch(GHLicense.class);
}
/**
* Returns a list all plans for your Marketplace listing
* <p>
* GitHub Apps must use a JWT to access this endpoint.
* <p>
* OAuth Apps must use basic authentication with their client ID and client secret to access this endpoint.
*
* @return the paged iterable
* @throws IOException
* the io exception
* @see <a href="https://developer.github.com/v3/apps/marketplace/#list-all-plans-for-your-marketplace-listing">List
* Plans</a>
*/
public PagedIterable<GHMarketplacePlan> listMarketplacePlans() throws IOException {
return createRequest().withUrlPath("/marketplace_listing/plans").toIterable(GHMarketplacePlan[].class, null);
}
/**
* Gets complete list of open invitations for current user.
*
* @return the my invitations
* @throws IOException
* the io exception
*/
public List<GHInvitation> getMyInvitations() throws IOException {
return createRequest().withUrlPath("/user/repository_invitations")
.toIterable(GHInvitation[].class, null)
.toList();
}
/**
* This method returns shallowly populated organizations.
* <p>
* To retrieve full organization details, you need to call {@link #getOrganization(String)} TODO: make this
* automatic.
*
* @return the my organizations
* @throws IOException
* the io exception
*/
public Map<String, GHOrganization> getMyOrganizations() throws IOException {
GHOrganization[] orgs = createRequest().withUrlPath("/user/orgs")
.toIterable(GHOrganization[].class, null)
.toArray();
Map<String, GHOrganization> r = new HashMap<>();
for (GHOrganization o : orgs) {
// don't put 'o' into orgs because they are shallow
r.put(o.getLogin(), o);
}
return r;
}
/**
* Returns only active subscriptions.
* <p>
* You must use a user-to-server OAuth access token, created for a user who has authorized your GitHub App, to
* access this endpoint
* <p>
* OAuth Apps must authenticate using an OAuth token.
*
* @return the paged iterable of GHMarketplaceUserPurchase
* @throws IOException
* the io exception
* @see <a href="https://developer.github.com/v3/apps/marketplace/#get-a-users-marketplace-purchases">Get a user's
* Marketplace purchases</a>
*/
public PagedIterable<GHMarketplaceUserPurchase> getMyMarketplacePurchases() throws IOException {
return createRequest().withUrlPath("/user/marketplace_purchases")
.toIterable(GHMarketplaceUserPurchase[].class, null);
}
/**
* Alias for {@link #getUserPublicOrganizations(String)}.
*
* @param user
* the user
* @return the user public organizations
* @throws IOException
* the io exception
*/
public Map<String, GHOrganization> getUserPublicOrganizations(GHUser user) throws IOException {
return getUserPublicOrganizations(user.getLogin());
}
/**
* This method returns a shallowly populated organizations.
* <p>
* To retrieve full organization details, you need to call {@link #getOrganization(String)}
*
* @param login
* the user to retrieve public Organization membership information for
* @return the public Organization memberships for the user
* @throws IOException
* the io exception
*/
public Map<String, GHOrganization> getUserPublicOrganizations(String login) throws IOException {
GHOrganization[] orgs = createRequest().withUrlPath("/users/" + login + "/orgs")
.toIterable(GHOrganization[].class, null)
.toArray();
Map<String, GHOrganization> r = new HashMap<>();
for (GHOrganization o : orgs) {
// don't put 'o' into orgs cache because they are shallow records
r.put(o.getLogin(), o);
}
return r;
}
/**
* Gets complete map of organizations/teams that current user belongs to.
* <p>
* Leverages the new GitHub API /user/teams made available recently to get in a single call the complete set of
* organizations, teams and permissions in a single call.
*
* @return the my teams
* @throws IOException
* the io exception
*/
public Map<String, Set<GHTeam>> getMyTeams() throws IOException {
Map<String, Set<GHTeam>> allMyTeams = new HashMap<>();
for (GHTeam team : createRequest().withUrlPath("/user/teams")
.toIterable(GHTeam[].class, item -> item.wrapUp(this))
.toArray()) {
String orgLogin = team.getOrganization().getLogin();
Set<GHTeam> teamsPerOrg = allMyTeams.get(orgLogin);
if (teamsPerOrg == null) {
teamsPerOrg = new HashSet<>();
}
teamsPerOrg.add(team);
allMyTeams.put(orgLogin, teamsPerOrg);
}
return allMyTeams;
}
/**
* Public events visible to you. Equivalent of what's displayed on https://github.com/
*
* @return the events
* @throws IOException
* the io exception
*/
public List<GHEventInfo> getEvents() throws IOException {
return createRequest().withUrlPath("/events").toIterable(GHEventInfo[].class, null).toList();
}
/**
* List public events for a user
* <a href="https://docs.github.com/en/rest/activity/events?apiVersion=2022-11-28#list-public-events-for-a-user">see
* API documentation</a>
*
* @param login
* the login (user) to look public events for
* @return the events
* @throws IOException
* the io exception
*/
public List<GHEventInfo> getUserPublicEvents(String login) throws IOException {
return createRequest().withUrlPath("/users/" + login + "/events/public")
.toIterable(GHEventInfo[].class, null)
.toList();
}
/**
* Gets a single gist by ID.
*
* @param id
* the id
* @return the gist
* @throws IOException
* the io exception
*/
public GHGist getGist(String id) throws IOException {
return createRequest().withUrlPath("/gists/" + id).fetch(GHGist.class);
}
/**
* Create gist gh gist builder.
*
* @return the gh gist builder
*/
public GHGistBuilder createGist() {
return new GHGistBuilder(this);
}
/**
* Parses the GitHub event object.
* <p>
* This is primarily intended for receiving a POST HTTP call from a hook. Unfortunately, hook script payloads aren't
* self-descriptive, so you need to know the type of the payload you are expecting.
*
* @param <T>
* the type parameter
* @param r
* the r
* @param type
* the type
* @return the t
* @throws IOException
* the io exception
*/
public <T extends GHEventPayload> T parseEventPayload(Reader r, Class<T> type) throws IOException {
T t = GitHubClient.getMappingObjectReader(this).forType(type).readValue(r);
t.lateBind();
return t;
}
/**
* Starts a builder that creates a new repository.
*
* <p>
* You use the returned builder to set various properties, then call {@link GHCreateRepositoryBuilder#create()} to
* finally create a repository.
*
* @param name
* the name
* @return the gh create repository builder
*/
public GHCreateRepositoryBuilder createRepository(String name) {
return new GHCreateRepositoryBuilder(name, this, "/user/repos");
}
/**
* Creates a new authorization.
* <p>
* The token created can be then used for {@link GitHub#connectUsingOAuth(String)} in the future.
*
* @param scope
* the scope
* @param note
* the note
* @param noteUrl
* the note url
* @return the gh authorization
* @throws IOException
* the io exception
* @see <a href="http://developer.github.com/v3/oauth/#create-a-new-authorization">Documentation</a>
*/
public GHAuthorization createToken(Collection<String> scope, String note, String noteUrl) throws IOException {
Requester requester = createRequest().with("scopes", scope).with("note", note).with("note_url", noteUrl);
return requester.method("POST").withUrlPath("/authorizations").fetch(GHAuthorization.class);
}
/**
* Creates a new authorization using an OTP.
* <p>
* Start by running createToken, if exception is thrown, prompt for OTP from user
* <p>
* Once OTP is received, call this token request
* <p>
* The token created can be then used for {@link GitHub#connectUsingOAuth(String)} in the future.
*
* @param scope
* the scope
* @param note
* the note
* @param noteUrl
* the note url
* @param OTP
* the otp
* @return the gh authorization
* @throws IOException
* the io exception
* @see <a href="http://developer.github.com/v3/oauth/#create-a-new-authorization">Documentation</a>
*/
public GHAuthorization createToken(Collection<String> scope, String note, String noteUrl, Supplier<String> OTP)
throws IOException {
try {
return createToken(scope, note, noteUrl);
} catch (GHOTPRequiredException ex) {
String OTPstring = OTP.get();
Requester requester = createRequest().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").withUrlPath("/authorizations").fetch(GHAuthorization.class);
}
}
/**
* Create or get auth gh authorization.
*
* @param clientId
* the client id
* @param clientSecret
* the client secret
* @param scopes
* the scopes
* @param note
* the note
* @param note_url
* the note url
* @return the gh authorization
* @throws IOException
* the io exception
* @see <a href=
* "https://developer.github.com/v3/oauth_authorizations/#get-or-create-an-authorization-for-a-specific-app">docs</a>
*/
public GHAuthorization createOrGetAuth(String clientId,
String clientSecret,
List<String> scopes,
String note,
String note_url) throws IOException {
Requester requester = createRequest().with("client_secret", clientSecret)
.with("scopes", scopes)
.with("note", note)
.with("note_url", note_url);
return requester.method("PUT").withUrlPath("/authorizations/clients/" + clientId).fetch(GHAuthorization.class);
}
/**
* Delete auth.
*
* @param id
* the id
* @throws IOException
* the io exception
* @see <a href="https://developer.github.com/v3/oauth_authorizations/#delete-an-authorization">Delete an
* authorization</a>
*/
public void deleteAuth(long id) throws IOException {
createRequest().method("DELETE").withUrlPath("/authorizations/" + id).send();
}
/**
* Check auth gh authorization.
*
* @param clientId
* the client id
* @param accessToken
* the access token
* @return the gh authorization
* @throws IOException
* the io exception
* @see <a href="https://developer.github.com/v3/oauth_authorizations/#check-an-authorization">Check an
* authorization</a>
*/
public GHAuthorization checkAuth(@Nonnull String clientId, @Nonnull String accessToken) throws IOException {
return createRequest().withUrlPath("/applications/" + clientId + "/tokens/" + accessToken)
.fetch(GHAuthorization.class);
}
/**
* Reset auth gh authorization.
*
* @param clientId
* the client id
* @param accessToken
* the access token
* @return the gh authorization
* @throws IOException
* the io exception
* @see <a href="https://developer.github.com/v3/oauth_authorizations/#reset-an-authorization">Reset an
* authorization</a>
*/
public GHAuthorization resetAuth(@Nonnull String clientId, @Nonnull String accessToken) throws IOException {
return createRequest().method("POST")
.withUrlPath("/applications/" + clientId + "/tokens/" + accessToken)
.fetch(GHAuthorization.class);
}
/**
* Returns a list of all authorizations.
*
* @return the paged iterable
* @throws IOException
* the io exception
* @see <a href="https://developer.github.com/v3/oauth_authorizations/#list-your-authorizations">List your
* authorizations</a>
*/
public PagedIterable<GHAuthorization> listMyAuthorizations() throws IOException {
return createRequest().withUrlPath("/authorizations").toIterable(GHAuthorization[].class, null);
}
/**
* Returns the GitHub App associated with the authentication credentials used.
* <p>
* You must use a JWT to access this endpoint.
*
* @return the app
* @throws IOException
* the io exception
* @see <a href="https://developer.github.com/v3/apps/#get-the-authenticated-github-app">Get the authenticated
* GitHub App</a>
*/
public GHApp getApp() throws IOException {
return createRequest().withUrlPath("/app").fetch(GHApp.class);
}
/**
* Returns the GitHub App identified by the given slug
*
* @param slug
* the slug of the application
* @return the app
* @throws IOException
* the IO exception
* @see <a href="https://docs.github.com/en/rest/apps/apps?apiVersion=2022-11-28#get-an-app">Get an app</a>
*/
public GHApp getApp(@Nonnull String slug) throws IOException {
return createRequest().withUrlPath("/apps/" + slug).fetch(GHApp.class);
}
/**
* Creates a GitHub App from a manifest.
*
* @param code
* temporary code returned during the manifest flow
* @return the app
* @throws IOException
* the IO exception
* @see <a href=
* "https://docs.github.com/en/rest/apps/apps?apiVersion=2022-11-28#create-a-github-app-from-a-manifest">Get an
* app</a>
*/
public GHAppFromManifest createAppFromManifest(@Nonnull String code) throws IOException {
return createRequest().method("POST")
.withUrlPath("/app-manifests/" + code + "/conversions")
.fetch(GHAppFromManifest.class);
}
/**
* Returns the GitHub App Installation associated with the authentication credentials used.
* <p>
* You must use an installation token to access this endpoint; otherwise consider {@link #getApp()} and its various
* ways of retrieving installations.
*
* @return the app
* @throws IOException
* the io exception
* @see <a href="https://docs.github.com/en/rest/apps/installations">GitHub App installations</a>
*/
public GHAuthenticatedAppInstallation getInstallation() throws IOException {
return new GHAuthenticatedAppInstallation(this);
}
/**
* Ensures that the credential is valid.
*
* @return the boolean
*/
public boolean isCredentialValid() {
return client.isCredentialValid();
}
/**
* Provides a list of GitHub's IP addresses.
*
* @return an instance of {@link GHMeta}
* @throws IOException
* if the credentials supplied are invalid or if you're trying to access it as a GitHub App via the JWT
* authentication
* @see <a href="https://developer.github.com/v3/meta/#meta">Get Meta</a>
*/
public GHMeta getMeta() throws IOException {
return this.sanityCachedMeta.get(() -> createRequest().withUrlPath("/meta").fetch(GHMeta.class));
}
/**
* Gets project.
*
* @param id
* the id
* @return the project
* @throws IOException
* the io exception
*/
public GHProject getProject(long id) throws IOException {
return createRequest().withUrlPath("/projects/" + id).fetch(GHProject.class);
}
/**
* Gets project column.
*
* @param id
* the id
* @return the project column
* @throws IOException
* the io exception
*/
public GHProjectColumn getProjectColumn(long id) throws IOException {
return createRequest().withUrlPath("/projects/columns/" + id).fetch(GHProjectColumn.class).lateBind(this);
}
/**
* Gets project card.
*
* @param id
* the id
* @return the project card
* @throws IOException
* the io exception
*/
public GHProjectCard getProjectCard(long id) throws IOException {
return createRequest().withUrlPath("/projects/columns/cards/" + id).fetch(GHProjectCard.class).lateBind(this);
}
/**
* Tests the connection.
*
* <p>
* Verify that the API URL and credentials are valid to access this GitHub.
*
* <p>
* This method returns normally if the endpoint is reachable and verified to be GitHub API URL. Otherwise this
* method throws {@link IOException} to indicate the problem.
*
* @throws IOException
* the io exception
*/
public void checkApiUrlValidity() throws IOException {
client.checkApiUrlValidity();
}
/**
* Search commits.
*
* @return the gh commit search builder
*/
public GHCommitSearchBuilder searchCommits() {
return new GHCommitSearchBuilder(this);
}
/**
* Search issues.
*
* @return the gh issue search builder
*/
public GHIssueSearchBuilder searchIssues() {
return new GHIssueSearchBuilder(this);
}
/**
* Search for pull requests.
*
* @return gh pull request search builder
*/
public GHPullRequestSearchBuilder searchPullRequests() {
return new GHPullRequestSearchBuilder(this);
}
/**
* Search users.
*
* @return the gh user search builder
*/
public GHUserSearchBuilder searchUsers() {
return new GHUserSearchBuilder(this);
}
/**
* Search repositories.
*
* @return the gh repository search builder
*/
public GHRepositorySearchBuilder searchRepositories() {
return new GHRepositorySearchBuilder(this);
}
/**
* Search content.
*
* @return the gh content search builder
*/
public GHContentSearchBuilder searchContent() {
return new GHContentSearchBuilder(this);
}
/**
* List all the notifications.
*
* @return the gh notification stream
*/
public GHNotificationStream listNotifications() {
return new GHNotificationStream(this, "/notifications");
}
/**
* This provides a dump of every public repository, in the order that they were created.
*
* @return the paged iterable
* @see <a href="https://developer.github.com/v3/repos/#list-all-public-repositories">documentation</a>
*/
public PagedIterable<GHRepository> listAllPublicRepositories() {
return listAllPublicRepositories(null);
}
/**
* This provides a dump of every public repository, in the order that they were created.
*
* @param since
* The numeric ID of the last Repository that you’ve seen. See {@link GHRepository#getId()}
* @return the paged iterable
* @see <a href="https://developer.github.com/v3/repos/#list-all-public-repositories">documentation</a>
*/
public PagedIterable<GHRepository> listAllPublicRepositories(final String since) {
return createRequest().with("since", since).withUrlPath("/repositories").toIterable(GHRepository[].class, null);
}
/**
* Render a Markdown document in raw mode.
*
* <p>
* It takes a Markdown document as plaintext and renders it as plain Markdown without a repository context (just
* like a README.md file is rendered – this is the simplest way to preview a readme online).
*
* @param text
* the text
* @return the reader
* @throws IOException
* the io exception
* @see GHRepository#renderMarkdown(String, MarkdownMode) GHRepository#renderMarkdown(String, MarkdownMode)
*/
public Reader renderMarkdown(String text) throws IOException {
return new InputStreamReader(
createRequest().method("POST")
.with(new ByteArrayInputStream(text.getBytes("UTF-8")))
.contentType("text/plain;charset=UTF-8")
.withUrlPath("/markdown/raw")
.fetchStream(Requester::copyInputStream),
"UTF-8");
}
/**
* Gets an {@link ObjectWriter} that can be used to convert data objects in this library to JSON.
*
* If you must convert data object in this library to JSON, the {@link ObjectWriter} returned by this method is the
* only supported way of doing so. This {@link ObjectWriter} can be used to convert any library data object to JSON
* without throwing an exception.
*
* WARNING: While the JSON generated is generally expected to be stable, it is not part of the API of this library
* and may change without warning. Use with extreme caution.
*
* @return an {@link ObjectWriter} instance that can be further configured.
*/
@Nonnull
public static ObjectWriter getMappingObjectWriter() {
return GitHubClient.getMappingObjectWriter();
}
/**
* Gets an {@link ObjectReader} that can be used to convert JSON into library data objects.
*
* If you must manually create library data objects from JSON, the {@link ObjectReader} returned by this method is
* the only supported way of doing so.
*
* WARNING: Objects generated from this method have limited functionality. They will not throw when being crated
* from valid JSON matching the expected object, but they are not guaranteed to be usable beyond that. Use with
* extreme caution.
*
* @return an {@link ObjectReader} instance that can be further configured.
*/
@Nonnull
public static ObjectReader getMappingObjectReader() {
return GitHubClient.getMappingObjectReader(GitHub.offline());
}
/**
* Gets the client.
*
* @return the client
*/
@Nonnull
GitHubClient getClient() {
return client;
}
/**
* Creates the request.
*
* @return the requester
*/
@Nonnull
Requester createRequest() {
Requester requester = new Requester(client);
requester.injectMappingValue(this);
if (!this.getClass().equals(GitHub.class)) {
// For classes that extend GitHub, treat them still as a GitHub instance
requester.injectMappingValue(GitHub.class.getName(), this);
}
return requester;
}
/**
* Intern.
*
* @param user
* the user
* @return the GH user
* @throws IOException
* Signals that an I/O exception has occurred.
*/
GHUser intern(GHUser user) throws IOException {
if (user != null) {
// if we already have this user in our map, get it
// if not, remember this new user
GHUser existingUser = users.putIfAbsent(user.getLogin(), user);
if (existingUser != null) {
user = existingUser;
}
}
return user;
}
private static final Logger LOGGER = Logger.getLogger(GitHub.class.getName());
}