blob: 20617a380446ea4b4dcaee93901fbe45c44f97ca [file] [log] [blame]
// Copyright 2015 The LUCI Authors.
//
// 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 identity
import (
"fmt"
"regexp"
"strings"
)
// Kind is enumeration of known identity kinds. See Identity.
type Kind string
const (
// Anonymous kind means no identity information is provided. Identity value
// is always 'anonymous'.
Anonymous Kind = "anonymous"
// Bot is used for bots authenticated via IP allowlist. Used primarily by
// Swarming. Identity value encodes bot ID.
Bot Kind = "bot"
// Project is used to convey that the request from one LUCI project to another
// is being made on behalf of some LUCI project. Identity value is project ID.
Project Kind = "project"
// Service is used for GAE apps using X-Appengine-Inbound-Appid header for
// authentication. Identity value is GAE app id.
Service Kind = "service"
// User is used for regular users. Identity value is email address.
User Kind = "user"
)
// KnownKinds is mapping between Kind and regexp for identity values.
//
// See also appengine/components/components/auth/model.py in luci-py.
var KnownKinds = map[Kind]*regexp.Regexp{
Anonymous: regexp.MustCompile(`^anonymous$`),
Bot: regexp.MustCompile(`^[0-9a-zA-Z_\-\.@]+$`),
Project: regexp.MustCompile(`^[a-z0-9\-_]+$`),
Service: regexp.MustCompile(`^[0-9a-zA-Z_\-\:\.]+$`),
User: regexp.MustCompile(`^[0-9a-zA-Z_\-\.\+\%]+@[0-9a-zA-Z_\-\.]+$`),
}
// Identity represents a caller that makes requests. A string of the form
// "kind:value" where 'kind' is one of Kind constant and meaning of 'value'
// depends on a kind (see comments for Kind values).
type Identity string
// AnonymousIdentity represents anonymous user.
const AnonymousIdentity Identity = "anonymous:anonymous"
// MakeIdentity ensures 'identity' string looks like a valid identity and
// returns it as Identity value.
func MakeIdentity(identity string) (Identity, error) {
id := Identity(identity)
if err := id.Validate(); err != nil {
return "", err
}
return id, nil
}
// Validate checks that the identity string is well-formed.
func (id Identity) Validate() error {
chunks := strings.SplitN(string(id), ":", 2)
if len(chunks) != 2 {
return fmt.Errorf("auth: bad identity string %q", id)
}
re := KnownKinds[Kind(chunks[0])]
if re == nil {
return fmt.Errorf("auth: bad identity kind %q", chunks[0])
}
if !re.MatchString(chunks[1]) {
return fmt.Errorf("auth: bad value %q for identity kind %q", chunks[1], chunks[0])
}
return nil
}
// Kind returns identity kind. If identity string is invalid returns Anonymous.
func (id Identity) Kind() Kind {
chunks := strings.SplitN(string(id), ":", 2)
if len(chunks) != 2 {
return Anonymous
}
return Kind(chunks[0])
}
// Value returns a valued encoded in the identity, e.g. for User identity kind
// it is user's email address. If identity string is invalid returns "anonymous".
func (id Identity) Value() string {
chunks := strings.SplitN(string(id), ":", 2)
if len(chunks) != 2 {
return "anonymous"
}
return chunks[1]
}
// Email returns user's email for identity with kind User or empty string for
// all other identity kinds. If identity string is undefined returns "".
func (id Identity) Email() string {
chunks := strings.SplitN(string(id), ":", 2)
if len(chunks) != 2 {
return ""
}
if Kind(chunks[0]) == User {
return chunks[1]
}
return ""
}