blob: 8a024f592024dbbb651e3179cbbd16daf986c572 [file] [log] [blame]
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you 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 store
import (
"crypto/sha1"
"encoding/json"
"fmt"
"sync"
"github.com/99designs/keyring"
"github.com/apache/pulsar-client-go/oauth2"
"github.com/apache/pulsar-client-go/oauth2/clock"
)
type KeyringStore struct {
kr keyring.Keyring
clock clock.Clock
lock sync.Mutex
}
// storedItem represents an item stored in the keyring
type storedItem struct {
Audience string
UserName string
Grant oauth2.AuthorizationGrant
}
// NewKeyringStore creates a store based on a keyring.
func NewKeyringStore(kr keyring.Keyring) (*KeyringStore, error) {
return &KeyringStore{
kr: kr,
clock: clock.RealClock{},
}, nil
}
var _ Store = &KeyringStore{}
func (f *KeyringStore) SaveGrant(audience string, grant oauth2.AuthorizationGrant) error {
f.lock.Lock()
defer f.lock.Unlock()
var err error
var userName string
switch grant.Type {
case oauth2.GrantTypeClientCredentials:
if grant.ClientCredentials == nil {
return ErrUnsupportedAuthData
}
userName = grant.ClientCredentials.ClientEmail
case oauth2.GrantTypeDeviceCode:
if grant.Token == nil {
return ErrUnsupportedAuthData
}
userName, err = oauth2.ExtractUserName(*grant.Token)
if err != nil {
return err
}
default:
return ErrUnsupportedAuthData
}
item := storedItem{
Audience: audience,
UserName: userName,
Grant: grant,
}
err = f.setItem(item)
if err != nil {
return err
}
return nil
}
func (f *KeyringStore) LoadGrant(audience string) (*oauth2.AuthorizationGrant, error) {
f.lock.Lock()
defer f.lock.Unlock()
item, err := f.getItem(audience)
if err != nil {
if err == keyring.ErrKeyNotFound {
return nil, ErrNoAuthenticationData
}
return nil, err
}
switch item.Grant.Type {
case oauth2.GrantTypeClientCredentials:
if item.Grant.ClientCredentials == nil {
return nil, ErrUnsupportedAuthData
}
case oauth2.GrantTypeDeviceCode:
if item.Grant.Token == nil {
return nil, ErrUnsupportedAuthData
}
default:
return nil, ErrUnsupportedAuthData
}
return &item.Grant, nil
}
func (f *KeyringStore) WhoAmI(audience string) (string, error) {
f.lock.Lock()
defer f.lock.Unlock()
key := hashKeyringKey(audience)
authItem, err := f.kr.Get(key)
if err != nil {
if err == keyring.ErrKeyNotFound {
return "", ErrNoAuthenticationData
}
return "", fmt.Errorf("unable to get information from the keyring: %v", err)
}
return authItem.Label, nil
}
func (f *KeyringStore) Logout() error {
f.lock.Lock()
defer f.lock.Unlock()
var err error
keys, err := f.kr.Keys()
if err != nil {
return fmt.Errorf("unable to get information from the keyring: %v", err)
}
for _, key := range keys {
err = f.kr.Remove(key)
}
if err != nil {
return fmt.Errorf("unable to update the keyring: %v", err)
}
return nil
}
func (f *KeyringStore) getItem(audience string) (storedItem, error) {
key := hashKeyringKey(audience)
i, err := f.kr.Get(key)
if err != nil {
return storedItem{}, err
}
var grant oauth2.AuthorizationGrant
err = json.Unmarshal(i.Data, &grant)
if err != nil {
// the grant appears to be invalid
return storedItem{}, ErrUnsupportedAuthData
}
return storedItem{
Audience: audience,
UserName: i.Label,
Grant: grant,
}, nil
}
func (f *KeyringStore) setItem(item storedItem) error {
key := hashKeyringKey(item.Audience)
data, err := json.Marshal(item.Grant)
if err != nil {
return err
}
i := keyring.Item{
Key: key,
Data: data,
Label: item.UserName,
Description: "authorization grant",
KeychainNotTrustApplication: false,
KeychainNotSynchronizable: false,
}
err = f.kr.Set(i)
if err != nil {
return fmt.Errorf("unable to update the keyring: %v", err)
}
return nil
}
// hashKeyringKey creates a safe key based on the given string
func hashKeyringKey(s string) string {
h := sha1.New()
h.Write([]byte(s))
bs := h.Sum(nil)
return fmt.Sprintf("%x", bs)
}