Skip to content
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
101 changes: 93 additions & 8 deletions cmd/auth/login.go
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@
Scope string
Recommend bool
Domains []string
Exclude []string
NoWait bool
DeviceCode string
}
Expand Down Expand Up @@ -62,11 +63,13 @@
}
cmdutil.SetSupportedIdentities(cmd, []string{"user"})

cmd.Flags().StringVar(&opts.Scope, "scope", "", "scopes to request (space- or comma-separated)")
cmd.Flags().StringVar(&opts.Scope, "scope", "", "scopes to request (space- or comma-separated). Combines additively with --domain/--recommend")
cmd.Flags().BoolVar(&opts.Recommend, "recommend", false, "request only recommended (auto-approve) scopes")
available := sortedKnownDomains()
cmd.Flags().StringSliceVar(&opts.Domains, "domain", nil,
fmt.Sprintf("domain (repeatable or comma-separated, e.g. --domain calendar,task)\navailable: %s, all", strings.Join(available, ", ")))
cmd.Flags().StringSliceVar(&opts.Exclude, "exclude", nil,
"scopes to exclude from the request (repeatable or comma-separated, e.g. --exclude drive:file:download)")
cmd.Flags().BoolVar(&opts.JSON, "json", false, "structured JSON output")
cmd.Flags().BoolVar(&opts.NoWait, "no-wait", false, "initiate device authorization and return immediately; use --device-code to complete")
cmd.Flags().StringVar(&opts.DeviceCode, "device-code", "", "poll and complete authorization with a device code from a previous --no-wait call")
Expand Down Expand Up @@ -158,6 +161,10 @@

hasAnyOption := opts.Scope != "" || opts.Recommend || len(selectedDomains) > 0

if len(opts.Exclude) > 0 && !hasAnyOption {
return output.ErrValidation("--exclude requires --scope, --domain, or --recommend to be specified")

Check warning on line 165 in cmd/auth/login.go

View check run for this annotation

Codecov / codecov/patch

cmd/auth/login.go#L165

Added line #L165 was not covered by tests
}

if !hasAnyOption {
if !opts.JSON && f.IOStreams.IsTerminal {
result, err := runInteractiveLogin(f.IOStreams, lang, msg)
Expand Down Expand Up @@ -191,12 +198,11 @@
// endpoint rejects raw "a,b" strings as a single malformed scope.
finalScope := normalizeScopeInput(opts.Scope)

// Resolve scopes from domain/permission filters
// Resolve scopes from domain/permission filters and merge with --scope.
// --scope, --domain, and --recommend combine additively so callers can,
// for example, request all `docs` scopes plus a few specific `drive`
// scopes in a single command.
if len(selectedDomains) > 0 || opts.Recommend {
if opts.Scope != "" {
return output.ErrValidation("cannot use --scope together with --domain/--recommend")
}

var candidateScopes []string
if len(selectedDomains) > 0 {
candidateScopes = collectScopesForDomains(selectedDomains, "user")
Expand All @@ -210,11 +216,35 @@
candidateScopes = registry.FilterAutoApproveScopes(candidateScopes)
}

if len(candidateScopes) == 0 {
if len(candidateScopes) == 0 && opts.Scope == "" {

Check warning on line 219 in cmd/auth/login.go

View check run for this annotation

Codecov / codecov/patch

cmd/auth/login.go#L219

Added line #L219 was not covered by tests
return output.ErrValidation("no matching scopes found, check domain/scope options")
}

finalScope = strings.Join(candidateScopes, " ")
// Merge --scope additively with the resolved domain scopes.
merged := make(map[string]bool, len(candidateScopes)+len(strings.Fields(finalScope)))
for _, s := range candidateScopes {
merged[s] = true

Check warning on line 226 in cmd/auth/login.go

View check run for this annotation

Codecov / codecov/patch

cmd/auth/login.go#L224-L226

Added lines #L224 - L226 were not covered by tests
}
for _, s := range strings.Fields(finalScope) {
merged[s] = true

Check warning on line 229 in cmd/auth/login.go

View check run for this annotation

Codecov / codecov/patch

cmd/auth/login.go#L228-L229

Added lines #L228 - L229 were not covered by tests
}
finalScope = joinSortedScopeSet(merged)

Check warning on line 231 in cmd/auth/login.go

View check run for this annotation

Codecov / codecov/patch

cmd/auth/login.go#L231

Added line #L231 was not covered by tests
Comment thread
coderabbitai[bot] marked this conversation as resolved.
}

// Apply --exclude on top of the resolved scope set. We honour exclude
// regardless of whether scopes came from --scope, --domain, --recommend,
// or any combination thereof.
if len(opts.Exclude) > 0 {
excluded, unknown := applyExcludeScopes(finalScope, opts.Exclude)
if len(unknown) > 0 {
return output.ErrValidation(
"these --exclude scopes are not present in the requested set: %s",
strings.Join(unknown, ", "))

Check warning on line 242 in cmd/auth/login.go

View check run for this annotation

Codecov / codecov/patch

cmd/auth/login.go#L238-L242

Added lines #L238 - L242 were not covered by tests
}
finalScope = excluded
if strings.TrimSpace(finalScope) == "" {
return output.ErrValidation("no scopes left after applying --exclude; nothing to authorize")

Check warning on line 246 in cmd/auth/login.go

View check run for this annotation

Codecov / codecov/patch

cmd/auth/login.go#L244-L246

Added lines #L244 - L246 were not covered by tests
}
}

// Step 1: Request device authorization
Expand Down Expand Up @@ -580,3 +610,58 @@
}
return ""
}

// joinSortedScopeSet returns a deterministic, space-separated scope string
// from a set, sorted alphabetically. Empty/blank scopes are dropped.
func joinSortedScopeSet(set map[string]bool) string {
out := make([]string, 0, len(set))
for s := range set {
if strings.TrimSpace(s) == "" {
continue

Check warning on line 620 in cmd/auth/login.go

View check run for this annotation

Codecov / codecov/patch

cmd/auth/login.go#L616-L620

Added lines #L616 - L620 were not covered by tests
}
out = append(out, s)

Check warning on line 622 in cmd/auth/login.go

View check run for this annotation

Codecov / codecov/patch

cmd/auth/login.go#L622

Added line #L622 was not covered by tests
}
sort.Strings(out)
return strings.Join(out, " ")

Check warning on line 625 in cmd/auth/login.go

View check run for this annotation

Codecov / codecov/patch

cmd/auth/login.go#L624-L625

Added lines #L624 - L625 were not covered by tests
}

// applyExcludeScopes removes the provided exclude entries from the requested
// scope string. Each --exclude flag value may itself contain comma- or
// whitespace-separated scopes. Returns the filtered scope string and any
// exclude entries that were not present in the requested set (callers can
// surface those as a validation error to catch typos like
// `--exclude drive:file:downlod`).
func applyExcludeScopes(requested string, excludes []string) (string, []string) {
requestedSet := make(map[string]bool)
for _, s := range strings.Fields(requested) {
requestedSet[s] = true

Check warning on line 637 in cmd/auth/login.go

View check run for this annotation

Codecov / codecov/patch

cmd/auth/login.go#L634-L637

Added lines #L634 - L637 were not covered by tests
}

excludeSet := make(map[string]bool)
for _, raw := range excludes {

Check warning on line 641 in cmd/auth/login.go

View check run for this annotation

Codecov / codecov/patch

cmd/auth/login.go#L640-L641

Added lines #L640 - L641 were not covered by tests
// --exclude already splits on commas (StringSliceVar), but also
// tolerate whitespace-separated entries inside a single value.
for _, s := range strings.Fields(strings.ReplaceAll(raw, ",", " ")) {
excludeSet[s] = true

Check warning on line 645 in cmd/auth/login.go

View check run for this annotation

Codecov / codecov/patch

cmd/auth/login.go#L644-L645

Added lines #L644 - L645 were not covered by tests
}
}

var unknown []string
for s := range excludeSet {
if !requestedSet[s] {
unknown = append(unknown, s)

Check warning on line 652 in cmd/auth/login.go

View check run for this annotation

Codecov / codecov/patch

cmd/auth/login.go#L649-L652

Added lines #L649 - L652 were not covered by tests
}
}
if len(unknown) > 0 {
sort.Strings(unknown)
return requested, unknown

Check warning on line 657 in cmd/auth/login.go

View check run for this annotation

Codecov / codecov/patch

cmd/auth/login.go#L655-L657

Added lines #L655 - L657 were not covered by tests
}

kept := make(map[string]bool, len(requestedSet))
for s := range requestedSet {
if !excludeSet[s] {
kept[s] = true

Check warning on line 663 in cmd/auth/login.go

View check run for this annotation

Codecov / codecov/patch

cmd/auth/login.go#L660-L663

Added lines #L660 - L663 were not covered by tests
}
}
return joinSortedScopeSet(kept), nil

Check warning on line 666 in cmd/auth/login.go

View check run for this annotation

Codecov / codecov/patch

cmd/auth/login.go#L666

Added line #L666 was not covered by tests
}
Comment thread
JackZhao10086 marked this conversation as resolved.
Loading