lib/tokensapi/hydra.go (96 lines of code) (raw):
// Copyright 2020 Google LLC.
//
// 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 tokensapi
import (
"context"
"strings"
"time"
"google.golang.org/grpc/codes" /* copybara-comment */
"google.golang.org/grpc/status" /* copybara-comment */
"bitbucket.org/creachadair/stringset" /* copybara-comment */
"github.com/GoogleCloudPlatform/healthcare-federated-access-services/lib/hydra" /* copybara-comment: hydra */
"github.com/GoogleCloudPlatform/healthcare-federated-access-services/lib/storage" /* copybara-comment: storage */
cpb "github.com/GoogleCloudPlatform/healthcare-federated-access-services/proto/common/v1" /* copybara-comment: go_proto */
topb "github.com/GoogleCloudPlatform/healthcare-federated-access-services/proto/store/tokens" /* copybara-comment: go_proto */
)
// Hydra tokens management, implments TokenProvider interface.
type Hydra struct {
hydraAdminURL string
issuer string
clients func(tx storage.Tx) (map[string]*cpb.Client, error)
}
// NewHydraTokenManager creates a Hydra token manger.
func NewHydraTokenManager(hydraAdminURL, issuer string, clients func(tx storage.Tx) (map[string]*cpb.Client, error)) *Hydra {
return &Hydra{
hydraAdminURL: hydraAdminURL,
issuer: issuer,
clients: clients,
}
}
// ListTokens lists the tokens.
func (s *Hydra) ListTokens(ctx context.Context, user string, store storage.Store, tx storage.Tx) ([]*Token, error) {
pendings, err := findUserPendingDeleteTokens(user, store, tx)
if err != nil {
return nil, err
}
sessions, err := hydra.ListConsents(httpClient, s.hydraAdminURL, user)
if err != nil {
return nil, err
}
clients, err := s.clients(tx)
if err != nil {
return nil, err
}
var tokens []*Token
for _, se := range sessions {
tid, err := hydra.ExtractTokenIDInConsentSession(se)
// legacy token does not have a "tid", but it would not able to use anymore since it it not refresh able.
if err != nil {
continue
}
// token is already in pending delete state can not use anymore.
if pendings.Contains(tid) {
continue
}
t := &Token{
User: user,
RawTokenID: tid,
TokenPrefix: s.TokenPrefix(),
IssuedAt: time.Time(se.HandledAt).Unix(),
Platform: s.TokenPrefix(),
Issuer: s.issuer,
Subject: se.ConsentRequest.Subject,
Audience: strings.Join(se.GrantedAudience, ","),
Scope: strings.Join(se.GrantedScope, " "),
}
if client, ok := clients[se.ConsentRequest.Client.Name]; ok {
t.ClientID = client.ClientId
t.ClientName = se.ConsentRequest.Client.Name
t.ClientUI = client.Ui
}
tokens = append(tokens, t)
}
return tokens, nil
}
func findUserPendingDeleteTokens(user string, store storage.Store, tx storage.Tx) (stringset.Set, error) {
res := stringset.Set{}
pending := &topb.PendingDeleteToken{}
offset := 0
for {
// maybe improve this with better query support like `id in [...]`.
pendings, err := store.MultiReadTx(storage.PendingDeleteTokenDatatype, storage.DefaultRealm, user, storage.MatchAllIDs, nil, offset, storage.MaxPageSize, pending, tx)
if err != nil {
return nil, status.Errorf(codes.Unavailable, "find pending delete token MultiReadTx() failed: %v", err)
}
for _, entry := range pendings.Entries {
res.Add(entry.ItemID)
offset++
}
// no more page
if pendings.MatchCount < storage.MaxPageSize {
break
}
}
return res, nil
}
// DeleteToken revokes a token.
func (s *Hydra) DeleteToken(ctx context.Context, user, tokenID string, store storage.Store, tx storage.Tx) error {
pending := &topb.PendingDeleteToken{}
if err := store.WriteTx(storage.PendingDeleteTokenDatatype, storage.DefaultRealm, user, tokenID, storage.LatestRev, pending, nil, tx); err != nil {
return status.Errorf(codes.Unavailable, "write PendingDeleteToken token failed: %v", err)
}
return nil
}
// TokenPrefix of Hydra provided tokens.
func (s *Hydra) TokenPrefix() string {
return "hydra"
}