blob: 56b93697203e198f0d56b1080021fce7f432035b [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
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// See the License for the specific language governing permissions and
// limitations under the License.
package identity
import (
// 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 whitelist. 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 used in Validate. It is mapping between Kind and regexp for
// identity value. See also appengine/components/components/auth/ 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-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 ""