go.chromium.org/luci@v0.0.0-20240309015107-7cdc2e660f33/server/auth/openid/discovery_doc.go (about)

     1  // Copyright 2020 The LUCI Authors.
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //      http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package openid
    16  
    17  import (
    18  	"context"
    19  	"time"
    20  
    21  	"go.chromium.org/luci/server/auth/internal"
    22  	"go.chromium.org/luci/server/caching"
    23  )
    24  
    25  // GoogleDiscoveryURL is an URL of the Google OpenID Connect discovery document.
    26  const GoogleDiscoveryURL = "https://accounts.google.com/.well-known/openid-configuration"
    27  
    28  var (
    29  	discoveryDocCache = caching.RegisterLRUCache[string, *DiscoveryDoc](8)
    30  	signingKeysCache  = caching.RegisterLRUCache[string, *JSONWebKeySet](8)
    31  )
    32  
    33  // DiscoveryDoc describes a subset of OpenID Discovery JSON document.
    34  //
    35  // See https://developers.google.com/identity/protocols/OpenIDConnect#discovery.
    36  type DiscoveryDoc struct {
    37  	Issuer                string `json:"issuer"`
    38  	AuthorizationEndpoint string `json:"authorization_endpoint"`
    39  	TokenEndpoint         string `json:"token_endpoint"`
    40  	RevocationEndpoint    string `json:"revocation_endpoint"`
    41  	JwksURI               string `json:"jwks_uri"`
    42  }
    43  
    44  // SigningKeys returns a JSON Web Key set fetched from the location specified
    45  // in the discovery document.
    46  //
    47  // It fetches them on the first use and then keeps them cached in the process
    48  // cache for 6h.
    49  //
    50  // May return both fatal and transient errors.
    51  func (d *DiscoveryDoc) SigningKeys(ctx context.Context) (*JSONWebKeySet, error) {
    52  	return signingKeysCache.LRU(ctx).GetOrCreate(ctx, d.JwksURI, func() (*JSONWebKeySet, time.Duration, error) {
    53  		raw := &JSONWebKeySetStruct{}
    54  		req := internal.Request{
    55  			Method: "GET",
    56  			URL:    d.JwksURI,
    57  			Out:    raw,
    58  		}
    59  		if err := req.Do(ctx); err != nil {
    60  			return nil, 0, err
    61  		}
    62  		keys, err := NewJSONWebKeySet(raw)
    63  		if err != nil {
    64  			return nil, 0, err
    65  		}
    66  		return keys, time.Hour * 6, nil
    67  	})
    68  }
    69  
    70  // FetchDiscoveryDoc fetches the discovery document from the given URL.
    71  //
    72  // It is cached in the process cache for 24 hours.
    73  func FetchDiscoveryDoc(ctx context.Context, url string) (*DiscoveryDoc, error) {
    74  	return discoveryDocCache.LRU(ctx).GetOrCreate(ctx, url, func() (*DiscoveryDoc, time.Duration, error) {
    75  		doc := &DiscoveryDoc{}
    76  		req := internal.Request{
    77  			Method: "GET",
    78  			URL:    url,
    79  			Out:    doc,
    80  		}
    81  		if err := req.Do(ctx); err != nil {
    82  			return nil, 0, err
    83  		}
    84  		return doc, time.Hour * 24, nil
    85  	})
    86  }