Skip to content

Commit

Permalink
Add ability to impersonate a user with G-suite domain wide delegation
Browse files Browse the repository at this point in the history
  • Loading branch information
Bowbaq committed May 26, 2020
1 parent b3946d5 commit 7c66d8e
Show file tree
Hide file tree
Showing 3 changed files with 102 additions and 99 deletions.
193 changes: 98 additions & 95 deletions client.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,69 +25,54 @@ type Client struct {
Drive *drive.Service
}

const (
sheetMimeType = "application/vnd.google-apps.spreadsheet"
)
func NewServiceAccountClientFromReader(creds io.Reader) (*Client, error) {
jwtJSON, err := ioutil.ReadAll(creds)
if err != nil {
return nil, errors.Wrap(err, "unable to read credentials")
}

func (c *Client) ShareFile(fileID, email string) error {
return c.shareFile(fileID, email, false)
}
config, err := google.JWTConfigFromJSON(jwtJSON, sheets.SpreadsheetsScope, drive.DriveScope)
if err != nil {
return nil, errors.Wrap(err, "unable to parse JWT config")
}

func (c *Client) ShareFileNotify(fileID, email string) error {
return c.shareFile(fileID, email, true)
return NewClientFromConfig(config)
}

func (c *Client) ShareWithAnyone(fileID string) error {
perm := drive.Permission{
Role: "writer",
Type: "anyone",

AllowFileDiscovery: false,
func NewImpersonatingServiceAccountClient(creds io.Reader, userEmail string) (*Client, error) {
jwtJSON, err := ioutil.ReadAll(creds)
if err != nil {
return nil, errors.Wrap(err, "unable to read credentials")
}

return googleRetry(func() error {
_, err := c.Drive.Permissions.Create(fileID, &perm).Do()
return err
})
}

func (c *Client) shareFile(fileID, email string, notify bool) error {
perm := drive.Permission{
EmailAddress: email,
Role: "writer",
Type: "user",
config, err := google.JWTConfigFromJSON(jwtJSON, sheets.SpreadsheetsScope, drive.DriveScope)
if err != nil {
return nil, errors.Wrap(err, "unable to parse JWT config")
}
req := c.Drive.Permissions.Create(fileID, &perm).SendNotificationEmail(notify)
config.Subject = userEmail

return googleRetry(func() error {
_, err := req.Do()
return err
})
return NewClientFromConfig(config)
}

func (c *Client) Revoke(fileID, email string) error {
var permissions *drive.PermissionList
err := googleRetry(func() error {
var rerr error
permissions, rerr = c.Drive.Permissions.List(fileID).Fields("nextPageToken, permissions(id, emailAddress, type, role)").Do()
func NewClientFromConfig(config *jwt.Config) (*Client, error) {
client := config.Client(context.Background())

return rerr
})
sheetsSrv, err := sheets.New(client)
if err != nil {
return errors.Wrapf(err, "couldn't list permissions for %s", fileID)
return nil, errors.Wrap(err, "couldn't initialize sheets client")
}

for _, p := range permissions.Permissions {
if p.EmailAddress != email {
continue
}

return googleRetry(func() error {
return c.Drive.Permissions.Delete(fileID, p.Id).Do()
})
driveSrv, err := drive.New(client)
if err != nil {
return nil, errors.Wrap(err, "couldn't initialize drive client")
}

return nil
return &Client{
JWTConfig: config,

Sheets: sheetsSrv,
Drive: driveSrv,
}, nil
}

func (c *Client) ListFiles(query string) ([]*drive.File, error) {
Expand Down Expand Up @@ -173,29 +158,6 @@ func (c *Client) CreateSpreadsheetWithData(title string, data [][]string) (*Spre
return ss, err
}

func (c *Client) Delete(fileId string) error {
req := c.Drive.Files.Delete(fileId)

return googleRetry(func() error {
return req.Do()
})
}

// Transfer ownership of the file
func (c *Client) TransferOwnership(fileID, email string) error {
perm := drive.Permission{
EmailAddress: email,
Role: "owner",
Type: "user",
}
req := c.Drive.Permissions.Create(fileID, &perm).TransferOwnership(true)

return googleRetry(func() error {
_, err := req.Do()
return err
})
}

func (c *Client) GetSpreadsheet(spreadsheetId string) (*Spreadsheet, error) {
var ssInfo *sheets.Spreadsheet
err := googleRetry(func() error {
Expand Down Expand Up @@ -226,47 +188,88 @@ func (c *Client) GetSpreadsheetWithData(spreadsheetId string) (*Spreadsheet, err
return &Spreadsheet{c, ssInfo}, nil
}

func getServiceAccountConfig(reader io.Reader) (*jwt.Config, error) {
b, err := ioutil.ReadAll(reader)
func (c *Client) Delete(fileId string) error {
req := c.Drive.Files.Delete(fileId)

if err != nil {
return nil, fmt.Errorf("Unable to read credentials file: %s", err)
return googleRetry(func() error {
return req.Do()
})
}

func (c *Client) ShareFile(fileID, email string) error {
return c.shareFile(fileID, email, false)
}

func (c *Client) ShareFileNotify(fileID, email string) error {
return c.shareFile(fileID, email, true)
}

func (c *Client) ShareWithAnyone(fileID string) error {
perm := drive.Permission{
Role: "writer",
Type: "anyone",

AllowFileDiscovery: false,
}

config, err := google.JWTConfigFromJSON(b, sheets.SpreadsheetsScope, drive.DriveScope)
if err != nil {
return nil, fmt.Errorf("Unable parse JWT config: %s", err)
return googleRetry(func() error {
_, err := c.Drive.Permissions.Create(fileID, &perm).Do()
return err
})
}

func (c *Client) shareFile(fileID, email string, notify bool) error {
perm := drive.Permission{
EmailAddress: email,
Role: "writer",
Type: "user",
}
req := c.Drive.Permissions.Create(fileID, &perm).SendNotificationEmail(notify)

return config, nil
return googleRetry(func() error {
_, err := req.Do()
return err
})
}

func NewServiceAccountClient(credsReader io.Reader) (*Client, error) {
config, err := getServiceAccountConfig(credsReader)
func (c *Client) Revoke(fileID, email string) error {
var permissions *drive.PermissionList
err := googleRetry(func() error {
var rerr error
permissions, rerr = c.Drive.Permissions.List(fileID).Fields("nextPageToken, permissions(id, emailAddress, type, role)").Do()

return rerr
})
if err != nil {
return nil, err
return errors.Wrapf(err, "couldn't list permissions for %s", fileID)
}

ctx := context.Background()
client := config.Client(ctx)
for _, p := range permissions.Permissions {
if p.EmailAddress != email {
continue
}

sheetsSrv, err := sheets.New(client)
if err != nil {
return nil, err
return googleRetry(func() error {
return c.Drive.Permissions.Delete(fileID, p.Id).Do()
})
}

driveSrv, err := drive.New(client)
if err != nil {
return nil, err
}
return nil
}

return &Client{
JWTConfig: config,
// Transfer ownership of the file
func (c *Client) TransferOwnership(fileID, email string) error {
perm := drive.Permission{
EmailAddress: email,
Role: "owner",
Type: "user",
}
req := c.Drive.Permissions.Create(fileID, &perm).TransferOwnership(true)

Sheets: sheetsSrv,
Drive: driveSrv,
}, nil
return googleRetry(func() error {
_, err := req.Do()
return err
})
}

func googleRetry(f func() error) error {
Expand Down
3 changes: 1 addition & 2 deletions client_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,9 +25,8 @@ var configTests = []struct {
}

func TestNewServiceAccountClient(t *testing.T) {

for _, tt := range configTests {
_, err := NewServiceAccountClient(strings.NewReader(tt.config))
_, err := NewServiceAccountClientFromReader(strings.NewReader(tt.config))

if tt.errExpected && err == nil {
t.Error("Expected error, but got none")
Expand Down
5 changes: 3 additions & 2 deletions spreadsheet.go
Original file line number Diff line number Diff line change
Expand Up @@ -118,8 +118,6 @@ func isFakeDuplicateSheetError(err error) bool {
hasSubsequentDuplicate = false
)
for i, e := range rerr.WrappedErrors() {
fmt.Fprintf(os.Stderr, "%d - %v\n", i, e)

if gerr, ok := e.(*googleapi.Error); ok {
if gerr.Code == 400 && strings.Contains(gerr.Message, "duplicateSheet") {
if i == 0 {
Expand All @@ -129,6 +127,9 @@ func isFakeDuplicateSheetError(err error) bool {
}
}
}
if e != nil {
fmt.Fprintf(os.Stderr, "%d - %v\n", i, e)
}
}

return firstErrorIsNotDuplicate && hasSubsequentDuplicate
Expand Down

0 comments on commit 7c66d8e

Please sign in to comment.