soumya92/barista

View on GitHub
samples/yubikey/yubikey.go

Summary

Maintainability
A
0 mins
Test Coverage
// Copyright 2018 Google Inc.
//
// 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 yubikey provides an indicator for a waiting yubikey.
package yubikey

import (
    "os"
    "path"
    "strings"
    "sync"

    "barista.run/bar"
    "barista.run/base/value"
    "barista.run/outputs"

    "github.com/maximbaz/yubikey-touch-detector/detector"
    ykNotifier "github.com/maximbaz/yubikey-touch-detector/notifier"
)

// Module represents a yubikey barista module that shows an indicator whenever
// the plugged-in yubikey is waiting for user input.
type Module struct {
    gpgPubringPath string
    outputFunc     value.Value // of func(bool, bool) bar.Output
}

// ForPath constructs a yubikey module with the given path to the gpg keyring.
func ForPath(gpgPubringPath string) *Module {
    m := &Module{
        gpgPubringPath: gpgPubringPath,
    }
    m.Output(func(gpg, u2f bool) bar.Output {
        reason := []string{}
        if gpg {
            reason = append(reason, "GPG")
        }
        if u2f {
            reason = append(reason, "U2F")
        }
        if len(reason) == 0 {
            return nil
        }
        return outputs.Textf("[YK: %s]", strings.Join(reason, ","))
    })
    return m
}

// New constructs a new yubikey module using the default paths for the u2f
// pending file and gpg keyring.
func New() *Module {
    gpgHome := os.Getenv("GNUPGHOME")
    if gpgHome != "" {
        return ForPath(path.Join(gpgHome, "pubring.kbx"))
    }
    return ForPath(os.ExpandEnv("$HOME/.gnupg/pubring.kbx"))
}

// Output sets the output format for the module.
func (m *Module) Output(outputFunc func(bool, bool) bar.Output) *Module {
    m.outputFunc.Set(outputFunc)
    return m
}

// Stream starts the module.
func (m *Module) Stream(sink bar.Sink) {
    ykChan := make(chan ykNotifier.Message, 10)
    notifiers := new(sync.Map)
    notifiers.Store("barista", ykChan)

    requestGPGCheck := make(chan bool)
    go detector.CheckGPGOnRequest(requestGPGCheck, notifiers)
    go detector.WatchU2F(notifiers)
    go detector.WatchGPG(m.gpgPubringPath, requestGPGCheck)

    exits := new(sync.Map)
    go detector.WatchSSH(requestGPGCheck, exits)
    defer func(exits *sync.Map) {
        exits.Range(func(_, value any) bool {
            if ch, ok := value.(chan bool); ok {
                ch <- true
            }
            return true
        })
    }(exits)

    gpg := false
    u2f := false
    outf := m.outputFunc.Get().(func(bool, bool) bar.Output)
    nextOutputFunc, done := m.outputFunc.Subscribe()
    defer done()
    for {
        sink.Output(outf(gpg, u2f))
        select {
        case msg := <-ykChan:
            switch msg {
            case ykNotifier.GPG_ON:
                gpg = true
            case ykNotifier.GPG_OFF:
                gpg = false
            case ykNotifier.U2F_ON:
                u2f = true
            case ykNotifier.U2F_OFF:
                u2f = false
            }
        case <-nextOutputFunc:
            outf = m.outputFunc.Get().(func(bool, bool) bar.Output)
        }
    }
}