%PDF- %PDF-
| Direktori : /home/waritko/go/src/github.com/odeke-em/drive/src/ |
| Current File : //home/waritko/go/src/github.com/odeke-em/drive/src/share.go |
// Copyright 2015 Google Inc. All Rights Reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package drive
import (
"fmt"
"strings"
"sync"
"github.com/odeke-em/log"
)
type AccountType int
const (
UnknownAccountType AccountType = 1 << iota
Anyone
User
Domain
Group
)
type Role int
const (
UnknownRole Role = 1 << iota
Owner
Reader
Writer
Commenter
)
const (
NoopOnShare = 1 << iota
Notify
WithLink
)
type shareChange struct {
emailMessage string
emails []string
roles []Role
accountTypes []AccountType
files []*File
revoke bool
notify bool
// withLink turns off public file indexing so that
// the file will only be shared to those with the link
withLink bool
}
type permission struct {
fileId string
value string
message string
role Role
accountType AccountType
notify bool
withLink bool
}
func (r *Role) String() string {
switch *r {
case Owner:
return "owner"
case Reader:
return "reader"
case Writer:
return "writer"
case Commenter:
return "commenter"
}
return "unknown"
}
func unknownRole(r string) bool {
return strings.ToLower(r) == "unknown"
}
func (a *AccountType) String() string {
switch *a {
case Anyone:
return "anyone"
case User:
return "user"
case Domain:
return "domain"
case Group:
return "group"
}
return "unknown"
}
func unknownAccountType(a string) bool {
return strings.ToLower(a) == "unknown"
}
func stringToRole() func(string) Role {
roleMap := make(map[string]Role)
roles := []Role{UnknownRole, Owner, Reader, Writer, Commenter}
for _, role := range roles {
roleMap[role.String()] = role
}
return func(s string) Role {
r, ok := roleMap[strings.ToLower(s)]
if !ok {
return Reader
}
return r
}
}
func stringToAccountType() func(string) AccountType {
accountMap := make(map[string]AccountType)
accounts := []AccountType{UnknownAccountType, Anyone, User, Domain, Group}
for _, account := range accounts {
accountMap[account.String()] = account
}
return func(s string) AccountType {
a, ok := accountMap[strings.ToLower(s)]
if !ok {
return User
}
return a
}
}
var reverseRoleResolve = stringToRole()
var reverseAccountTypeResolve = stringToAccountType()
func reverseRolesResolver(roleArgv ...string) (roles []Role) {
alreadySeen := map[Role]bool{}
for _, roleStr := range roleArgv {
role := reverseRoleResolve(roleStr)
if _, seen := alreadySeen[role]; !seen {
roles = append(roles, role)
alreadySeen[role] = true
}
}
return roles
}
func reverseAccountTypesResolver(accArgv ...string) (accountTypes []AccountType) {
for _, accStr := range accArgv {
accountTypes = append(accountTypes, reverseAccountTypeResolve(accStr))
}
return accountTypes
}
func (g *Commands) resolveRemotePaths(relToRootPaths []string, byId bool) (files []*File) {
var wg sync.WaitGroup
resolver := g.rem.FindByPath
if byId {
resolver = g.rem.FindById
}
wg.Add(len(relToRootPaths))
for _, relToRoot := range relToRootPaths {
go func(p string, wgg *sync.WaitGroup) {
defer wgg.Done()
file, err := resolver(p)
if err != nil || file == nil {
return
}
files = append(files, file)
}(relToRoot, &wg)
}
wg.Wait()
return files
}
func emailsToIds(g *Commands, emails []string) map[string]string {
emailToIds := make(map[string]string)
var wg sync.WaitGroup
wg.Add(len(emails))
for _, email := range emails {
go func(email string, wgg *sync.WaitGroup) {
defer wgg.Done()
emailId, err := g.rem.idForEmail(email)
if err == nil {
emailToIds[email] = emailId
}
}(email, &wg)
}
wg.Wait()
return emailToIds
}
func (c *Commands) Unshare(byId bool) (err error) {
return c.share(true, byId)
}
func (c *Commands) Share(byId bool) (err error) {
return c.share(false, byId)
}
func showPromptShareChanges(logy *log.Logger, change *shareChange) Agreement {
if len(change.files) < 1 {
return NotApplicable
}
verb := "unshare"
shareInfo := "Revoke access"
extraShareInfo := ""
if !change.revoke {
shareInfo = "Provide access"
if len(change.emails) < 1 {
return NotApplicable
}
verb = "share"
if change.notify && len(change.emailMessage) >= 1 {
extraShareInfo = fmt.Sprintf("Message:\n\t\033[33m%s\033[00m\n", change.emailMessage)
}
}
if len(change.accountTypes) >= 1 {
logy.Logf("%s for accountType(s)\n", shareInfo)
for _, accountType := range change.accountTypes {
logy.Logf("\t\033[32m%s\033[00m\n", accountType.String())
}
logy.Logln(extraShareInfo)
}
if len(change.roles) >= 1 {
logy.Logln("For roles(s)")
for _, role := range change.roles {
logy.Logf("\t\033[33m%s\033[00m\n", role.String())
}
}
if len(change.emails) >= 1 {
logy.Logf("\nAddressees:\n")
for _, email := range change.emails {
if email == "" {
email = "Anyone with the link"
}
logy.Logf("\t\033[92m+\033[00m %s\n", email)
}
}
logy.Logf("\nFile(s) to %s:\n", verb)
for _, file := range change.files {
if file == nil {
continue
}
logy.Logf("\t\033[92m+\033[00m %s\n", file.Name)
}
logy.Logln()
return promptForChanges()
}
func (c *Commands) playShareChanges(change *shareChange) (err error) {
if c.opts.canPrompt() {
if status := showPromptShareChanges(c.log, change); !accepted(status) {
return status.Error()
}
}
fnName := "unshare"
fn := c.rem.revokePermissions
if !change.revoke {
fnName = "share"
fn = func(perm *permission) error {
_, err := c.rem.insertPermissions(perm)
return err
}
}
successes := 0
for _, file := range change.files {
for _, accountType := range change.accountTypes {
for _, email := range change.emails {
if accountType == Anyone && email != "" {
// It doesn't make sense to share a file with
// permission "anyone" yet provide an email
continue
}
for _, role := range change.roles {
perm := permission{
fileId: file.Id,
value: email,
role: role,
accountType: accountType,
notify: change.notify,
message: change.emailMessage,
withLink: change.withLink,
}
if ferr := fn(&perm); ferr != nil {
err = reComposeError(err, fmt.Sprintf("%s err %s: %v\n", fnName, file.Name, ferr))
} else {
successes += 1
if c.opts.Verbose {
c.log.Logf("successful %s for %s with email %q, role %q accountType %q\n",
fnName, file.Name, email, role.String(), accountType.String())
}
}
}
}
}
}
if err != nil {
return err
}
if successes < 1 {
return noMatchesFoundErr(fmt.Errorf("no matches found!"))
}
return nil
}
func (c *Commands) share(revoke, byId bool) (err error) {
files := c.resolveRemotePaths(c.opts.Sources, byId)
var emails []string
var emailMessage string
roles := []Role{}
// By default, if they don't specify any
// account types to invoke, let's assume User.
// See Issue https://github.com/odeke-em/drive/issues/747.
accountTypes := []AccountType{User}
if revoke {
// In case of unsharing, when a user doesn't specify the
// roles, the addressee should be removed from all roles
roles = append(roles, Reader, Writer, Commenter)
} else {
roles = append(roles, Reader)
}
meta := *c.opts.Meta
if meta != nil {
emailList, eOk := meta[EmailsKey]
if eOk {
emails = emailList
if false {
emailIdMap := emailsToIds(c, emailList)
c.log.Logln(emailIdMap)
}
}
roleList, rOk := meta[RoleKey]
if rOk && len(roleList) >= 1 {
roles = reverseRolesResolver(roleList...)
}
accountTypeList, aOk := meta[AccountTypeKey]
if aOk && len(accountTypeList) >= 1 {
accountTypes = reverseAccountTypesResolver(accountTypeList...)
}
emailMessageList, emOk := meta[EmailMessageKey]
if emOk && len(emailMessageList) >= 1 {
emailMessage = strings.Join(emailMessageList, "\n")
}
}
if revoke && len(emails) < 1 {
// Account for case for unshare where certain types do not include the emails
// e.g revoking access to the entire domain, or user groups yet no email
// can be found
emails = append(emails, "")
}
anyoneAlreadySet := false
// Now here we have to match up emails with roles
// See Issue #568. If we've requested say `anyone`, this role needs no email
// address, so we should add an empty "" to the list of emails
for _, accountType := range accountTypes {
if accountType == Anyone {
anyoneAlreadySet = true
emails = append(emails, "")
}
}
change := shareChange{
files: files,
revoke: revoke,
roles: roles,
emails: emails,
accountTypes: accountTypes,
emailMessage: emailMessage,
notify: (c.opts.TypeMask & Notify) == Notify,
withLink: (c.opts.TypeMask & WithLink) == WithLink,
}
if len(change.emails) < 1 && change.withLink { // They've basically requested a share to everyone but with link
change.emails = append(change.emails, "")
if !anyoneAlreadySet {
change.accountTypes = append(change.accountTypes, Anyone)
}
}
return c.playShareChanges(&change)
}