blob: 222c3bc8f994ab569f18b8c959278f8cd915dc35 [file] [log] [blame]
// Copyright 2020 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 openid
import (
"context"
"strings"
"go.chromium.org/luci/auth/identity"
"go.chromium.org/luci/common/errors"
"go.chromium.org/luci/server/auth"
)
// UserFromIDToken validates the ID token and extracts user information from it.
//
// Returns the partially validated token and auth.User extracted from it.
//
// The caller is still responsible to verify token's Audience field.
func UserFromIDToken(ctx context.Context, token string, discovery *DiscoveryDoc) (*IDToken, *auth.User, error) {
// Validate the discovery doc has necessary fields to proceed.
switch {
case discovery.Issuer == "":
return nil, nil, errors.Reason("openid: bad discovery doc, empty issuer").Err()
case discovery.JwksURI == "":
return nil, nil, errors.Reason("openid: bad discovery doc, empty jwks_uri").Err()
}
// Grab the signing keys needed to verify the token. This is almost always
// hitting the local process cache and thus must be fast.
signingKeys, err := discovery.SigningKeys(ctx)
if err != nil {
return nil, nil, err
}
// Unpack the ID token to grab the user information from it.
verifiedToken, err := VerifyIDToken(ctx, token, signingKeys, discovery.Issuer)
if err != nil {
return nil, nil, err
}
// Ignore non https:// URLs for pictures. We serve all pages over HTTPS and
// don't want to break this rule just for a pretty picture.
picture := verifiedToken.Picture
if picture != "" && !strings.HasPrefix(picture, "https://") {
picture = ""
}
// Build the identity string from the email. This essentially validates it
// against a regexp.
id, err := identity.MakeIdentity("user:" + verifiedToken.Email)
if err != nil {
return nil, nil, err
}
return verifiedToken, &auth.User{
Identity: id,
Email: verifiedToken.Email,
Name: verifiedToken.Name,
Picture: picture,
}, nil
}