diff --git a/github/apps.go b/github/apps.go index f675a9abd18..5041e801280 100644 --- a/github/apps.go +++ b/github/apps.go @@ -32,8 +32,21 @@ type App struct { // InstallationToken represents an installation token. type InstallationToken struct { - Token *string `json:"token,omitempty"` - ExpiresAt *time.Time `json:"expires_at,omitempty"` + Token *string `json:"token,omitempty"` + ExpiresAt *time.Time `json:"expires_at,omitempty"` + Permissions *InstallationPermissions `json:"permissions,omitempty"` + Repositories []*Repository `json:"repositories,omitempty"` +} + +// InstallationTokenOptions allow restricting a token's access to specific repositories. +type InstallationTokenOptions struct { + // The IDs of the repositories that the installation token can access. + // Providing repository IDs restricts the access of an installation token to specific repositories. + RepositoryIDs []int64 `json:"repository_ids,omitempty"` + + // The permissions granted to the access token. + // The permissions object includes the permission names and their access type. + Permissions *InstallationPermissions `json:"permissions,omitempty"` } // InstallationPermissions lists the repository and organization permissions for an installation. @@ -194,10 +207,10 @@ func (s *AppsService) ListUserInstallations(ctx context.Context, opt *ListOption // CreateInstallationToken creates a new installation token. // // GitHub API docs: https://developer.github.com/v3/apps/#create-a-new-installation-token -func (s *AppsService) CreateInstallationToken(ctx context.Context, id int64) (*InstallationToken, *Response, error) { +func (s *AppsService) CreateInstallationToken(ctx context.Context, id int64, opt *InstallationTokenOptions) (*InstallationToken, *Response, error) { u := fmt.Sprintf("app/installations/%v/access_tokens", id) - req, err := s.client.NewRequest("POST", u, nil) + req, err := s.client.NewRequest("POST", u, opt) if err != nil { return nil, nil, err } diff --git a/github/apps_test.go b/github/apps_test.go index 8d5b27d38ce..55ef2a095ae 100644 --- a/github/apps_test.go +++ b/github/apps_test.go @@ -6,8 +6,12 @@ package github import ( + "bytes" "context" + "encoding/json" "fmt" + "io" + "io/ioutil" "net/http" "reflect" "testing" @@ -214,7 +218,54 @@ func TestAppsService_CreateInstallationToken(t *testing.T) { fmt.Fprint(w, `{"token":"t"}`) }) - token, _, err := client.Apps.CreateInstallationToken(context.Background(), 1) + token, _, err := client.Apps.CreateInstallationToken(context.Background(), 1, nil) + if err != nil { + t.Errorf("Apps.CreateInstallationToken returned error: %v", err) + } + + want := &InstallationToken{Token: String("t")} + if !reflect.DeepEqual(token, want) { + t.Errorf("Apps.CreateInstallationToken returned %+v, want %+v", token, want) + } +} + +func TestAppsService_CreateInstallationTokenWithOptions(t *testing.T) { + client, mux, _, teardown := setup() + defer teardown() + + installationTokenOptions := &InstallationTokenOptions{ + RepositoryIDs: []int64{1234}, + Permissions: &InstallationPermissions{ + Contents: String("write"), + Issues: String("read"), + }, + } + + // Convert InstallationTokenOptions into an io.ReadCloser object for comparison. + wantBody, err := GetReadCloser(installationTokenOptions) + if err != nil { + t.Errorf("GetReadCloser returned error: %v", err) + } + + mux.HandleFunc("/app/installations/1/access_tokens", func(w http.ResponseWriter, r *http.Request) { + // Read request body contents. + var gotBodyBytes []byte + gotBodyBytes, err = ioutil.ReadAll(r.Body) + if err != nil { + t.Errorf("ReadAll returned error: %v", err) + } + r.Body = ioutil.NopCloser(bytes.NewBuffer(gotBodyBytes)) + + if !reflect.DeepEqual(r.Body, wantBody) { + t.Errorf("request sent %+v, want %+v", r.Body, wantBody) + } + + testMethod(t, r, "POST") + testHeader(t, r, "Accept", mediaTypeIntegrationPreview) + fmt.Fprint(w, `{"token":"t"}`) + }) + + token, _, err := client.Apps.CreateInstallationToken(context.Background(), 1, installationTokenOptions) if err != nil { t.Errorf("Apps.CreateInstallationToken returned error: %v", err) } @@ -247,6 +298,7 @@ func TestAppsService_CreateAttachement(t *testing.T) { t.Errorf("CreateAttachment = %+v, want %+v", got, want) } } + func TestAppsService_FindOrganizationInstallation(t *testing.T) { client, mux, _, teardown := setup() defer teardown() @@ -330,3 +382,31 @@ func TestAppsService_FindUserInstallation(t *testing.T) { t.Errorf("Apps.FindUserInstallation returned %+v, want %+v", installation, want) } } + +// GetReadWriter converts a body interface into an io.ReadWriter object. +func GetReadWriter(body interface{}) (io.ReadWriter, error) { + var buf io.ReadWriter + if body != nil { + buf = new(bytes.Buffer) + enc := json.NewEncoder(buf) + err := enc.Encode(body) + if err != nil { + return nil, err + } + } + return buf, nil +} + +// GetReadCloser converts a body interface into an io.ReadCloser object. +func GetReadCloser(body interface{}) (io.ReadCloser, error) { + buf, err := GetReadWriter(body) + if err != nil { + return nil, err + } + + all, err := ioutil.ReadAll(buf) + if err != nil { + return nil, err + } + return ioutil.NopCloser(bytes.NewBuffer(all)), nil +} diff --git a/github/github-accessors.go b/github/github-accessors.go index b4ca033d300..f4d1984d334 100644 --- a/github/github-accessors.go +++ b/github/github-accessors.go @@ -3884,6 +3884,14 @@ func (i *InstallationToken) GetExpiresAt() time.Time { return *i.ExpiresAt } +// GetPermissions returns the Permissions field. +func (i *InstallationToken) GetPermissions() *InstallationPermissions { + if i == nil { + return nil + } + return i.Permissions +} + // GetToken returns the Token field if it's non-nil, zero value otherwise. func (i *InstallationToken) GetToken() string { if i == nil || i.Token == nil { @@ -3892,6 +3900,14 @@ func (i *InstallationToken) GetToken() string { return *i.Token } +// GetPermissions returns the Permissions field. +func (i *InstallationTokenOptions) GetPermissions() *InstallationPermissions { + if i == nil { + return nil + } + return i.Permissions +} + // GetExpiresAt returns the ExpiresAt field if it's non-nil, zero value otherwise. func (i *InteractionRestriction) GetExpiresAt() Timestamp { if i == nil || i.ExpiresAt == nil {