Index: go/src/infra/libs/auth/service.go |
diff --git a/go/src/infra/libs/auth/service.go b/go/src/infra/libs/auth/service.go |
deleted file mode 100644 |
index 401be28baa4cff6b9bfe1631f5761afee938eb73..0000000000000000000000000000000000000000 |
--- a/go/src/infra/libs/auth/service.go |
+++ /dev/null |
@@ -1,251 +0,0 @@ |
-// Copyright 2015 The Chromium Authors. All rights reserved. |
-// Use of this source code is governed by a BSD-style license that can be |
-// found in the LICENSE file. |
- |
-package auth |
- |
-import ( |
- "encoding/json" |
- "errors" |
- "fmt" |
- "io/ioutil" |
- "net/http" |
- "strings" |
- "time" |
- |
- "infra/libs/logging" |
-) |
- |
-// ServiceURL is URL of a service to talk to by default. |
-const ServiceURL string = "https://chrome-infra-auth.appspot.com" |
- |
-// IdentityKind is enum like type with possible kinds of identities. |
-type IdentityKind string |
- |
-const ( |
- // IdentityKindUnknown is used when server return identity kind not recognized |
- // by the client. |
- IdentityKindUnknown IdentityKind = "" |
- // IdentityKindAnonymous is used to represent anonymous callers. |
- IdentityKindAnonymous IdentityKind = "anonymous" |
- // IdentityKindBot is used to represent bots. Identity name is bot's IP. |
- IdentityKindBot IdentityKind = "bot" |
- // IdentityKindService is used to represent AppEngine apps when they use |
- // X-Appengine-Inbound-AppId header to authenticate. Identity name is app ID. |
- IdentityKindService IdentityKind = "service" |
- // IdentityKindUser is used to represent end users or OAuth service accounts. |
- // Identity name is an associated email (user email or service account email). |
- IdentityKindUser IdentityKind = "user" |
-) |
- |
-var ( |
- // ErrAccessDenied is returned by GroupsService methods if caller doesn't have |
- // enough permissions to execute an action. Corresponds to 403 HTTP status. |
- ErrAccessDenied = errors.New("Access denied (HTTP 403)") |
- |
- // ErrNoSuchItem is returned by GroupsService methods if requested item |
- // (e.g. a group) doesn't exist. Corresponds to 404 HTTP status. |
- ErrNoSuchItem = errors.New("No such item (HTTP 404)") |
-) |
- |
-// Identity represents some caller that can make requests. It generalizes |
-// accounts of real people, bot accounts and service-to-service accounts. |
-type Identity struct { |
- // Kind describes what sort of identity this struct represents. |
- Kind IdentityKind |
- // Name defines concrete instance of identity, its meaning depends on kind. |
- Name string |
-} |
- |
-// Group is a named list of identities, included subgroups and glob-like |
-// patterns (e.g. "user:*@example.com). |
-type Group struct { |
- // Name is a name of the group. |
- Name string |
- // Description is a human readable description of the group. |
- Description string |
- // Members is a list of identities included in the group explicitly. |
- Members []Identity |
- // Globs is a list of glob-like patterns for identities. |
- Globs []string |
- // Nested is a list of group names included into this group. |
- Nested []string |
-} |
- |
-// String returns human readable representation of Identity. |
-func (i Identity) String() string { |
- switch i.Kind { |
- case IdentityKindUnknown: |
- return "unknown" |
- case IdentityKindAnonymous: |
- return "anonymous" |
- case IdentityKindUser: |
- return i.Name |
- default: |
- return fmt.Sprintf("%s:%s", i.Kind, i.Name) |
- } |
-} |
- |
-// GroupsService knows how to talk to Groups API backend. Server side code |
-// is in https://github.com/luci/luci-py repository. |
-type GroupsService struct { |
- client *http.Client |
- serviceURL string |
- logger logging.Logger |
-} |
- |
-// NewGroupsService constructs new instance of GroupsService that talks to given |
-// service URL via given http.Client. If url is empty string, the default |
-// backend will be used. If httpClient is nil, http.DefaultClient will be used. |
-func NewGroupsService(url string, client *http.Client, logger logging.Logger) *GroupsService { |
- if url == "" { |
- url = ServiceURL |
- } |
- if client == nil { |
- client = http.DefaultClient |
- } |
- if logger == nil { |
- logger = logging.Null() |
- } |
- return &GroupsService{ |
- client: client, |
- serviceURL: url, |
- logger: logger, |
- } |
-} |
- |
-// ServiceURL returns a string with root URL of a Groups backend. |
-func (s *GroupsService) ServiceURL() string { |
- return s.serviceURL |
-} |
- |
-// FetchCallerIdentity returns caller's own Identity as seen by the server. |
-func (s *GroupsService) FetchCallerIdentity() (Identity, error) { |
- var response struct { |
- Identity string `json:"identity"` |
- } |
- err := s.doGet("/auth/api/v1/accounts/self", &response) |
- if err != nil { |
- return Identity{}, err |
- } |
- return parseIdentity(response.Identity) |
-} |
- |
-// FetchGroup returns a group definition. It does not fetch nested groups. |
-// Returns ErrNoSuchItem error if no such group. |
-func (s *GroupsService) FetchGroup(name string) (Group, error) { |
- var response struct { |
- Group struct { |
- Name string `json:"name"` |
- Description string `json:"description"` |
- Members []string `json:"members"` |
- Globs []string `json:"globs"` |
- Nested []string `json:"nested"` |
- } `json:"group"` |
- } |
- err := s.doGet("/auth/api/v1/groups/"+name, &response) |
- if err != nil { |
- return Group{}, err |
- } |
- if response.Group.Name != name { |
- return Group{}, fmt.Errorf( |
- "Unexpected group name in server response: '%s', expecting '%s'", |
- response.Group.Name, name) |
- } |
- g := Group{ |
- Name: response.Group.Name, |
- Description: response.Group.Description, |
- Members: make([]Identity, 0, len(response.Group.Members)), |
- Globs: response.Group.Globs, |
- Nested: response.Group.Nested, |
- } |
- for _, str := range response.Group.Members { |
- ident, err := parseIdentity(str) |
- if err != nil { |
- s.logger.Warningf("auth: failed to parse an identity in a group, ignoring it - %s", err) |
- } else { |
- g.Members = append(g.Members, ident) |
- } |
- } |
- return g, nil |
-} |
- |
-// doGet sends GET HTTP requests and decodes JSON into response. It retries |
-// multiple times on transient errors. |
-func (s *GroupsService) doGet(path string, response interface{}) error { |
- if len(path) == 0 || path[0] != '/' { |
- return fmt.Errorf("Path should start with '/': %s", path) |
- } |
- url := s.serviceURL + path |
- for attempt := 0; attempt < 5; attempt++ { |
- if attempt != 0 { |
- s.logger.Warningf("auth: retrying request to %s", url) |
- sleep(2 * time.Second) |
- } |
- req, err := http.NewRequest("GET", url, nil) |
- if err != nil { |
- return err |
- } |
- resp, err := doRequest(s.client, req) |
- if err != nil { |
- return err |
- } |
- // Success? |
- if resp.StatusCode < 300 { |
- defer resp.Body.Close() |
- return json.NewDecoder(resp.Body).Decode(response) |
- } |
- // Fatal error? |
- if resp.StatusCode >= 300 && resp.StatusCode < 500 { |
- defer resp.Body.Close() |
- switch resp.StatusCode { |
- case 403: |
- return ErrAccessDenied |
- case 404: |
- return ErrNoSuchItem |
- default: |
- body, _ := ioutil.ReadAll(resp.Body) |
- return fmt.Errorf("Unexpected reply (HTTP %d):\n%s", resp.StatusCode, string(body)) |
- } |
- } |
- // Retry. |
- resp.Body.Close() |
- } |
- return fmt.Errorf("Request to %s failed after 5 attempts", url) |
-} |
- |
-// parseIdentity takes a string of form "<kind>:<name>" and returns Identity |
-// struct. |
-func parseIdentity(str string) (Identity, error) { |
- chunks := strings.Split(str, ":") |
- if len(chunks) != 2 { |
- return Identity{}, fmt.Errorf("Invalid identity string: '%s'", str) |
- } |
- kind := IdentityKind(chunks[0]) |
- name := chunks[1] |
- switch kind { |
- case IdentityKindAnonymous: |
- if name != "anonymous" { |
- return Identity{}, fmt.Errorf("Invalid anonymous identity: '%s'", str) |
- } |
- return Identity{ |
- Kind: IdentityKindAnonymous, |
- Name: "anonymous", |
- }, nil |
- case IdentityKindBot, IdentityKindService, IdentityKindUser: |
- return Identity{ |
- Kind: kind, |
- Name: name, |
- }, nil |
- default: |
- return Identity{}, fmt.Errorf("Unrecognized identity kind: '%s'", str) |
- } |
-} |
- |
-// sleep is mocked in tests. |
-var sleep = time.Sleep |
- |
-// doRequest is mocked in tests. |
-var doRequest = func(c *http.Client, req *http.Request) (*http.Response, error) { |
- return c.Do(req) |
-} |