| // 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.GetMetadata(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) |
| } |