github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/apiserver/authentication/interactions.go (about)

     1  // Copyright 2016 Canonical Ltd. All rights reserved.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package authentication
     5  
     6  import (
     7  	"crypto/rand"
     8  	"fmt"
     9  	"sync"
    10  	"time"
    11  
    12  	"github.com/juju/errors"
    13  	"gopkg.in/juju/names.v2"
    14  )
    15  
    16  // ErrWaitCanceled is returned by Interactions.Wait when the cancel
    17  // channel is signalled.
    18  var ErrWaitCanceled = errors.New("wait canceled")
    19  
    20  // ErrExpired is returned by Interactions.Wait when interactions expire
    21  // before they are done.
    22  var ErrExpired = errors.New("interaction timed out")
    23  
    24  // Interactions maintains a set of Interactions.
    25  type Interactions struct {
    26  	mu    sync.Mutex
    27  	items map[string]*item
    28  }
    29  
    30  type item struct {
    31  	c        chan Interaction
    32  	caveatId []byte
    33  	expiry   time.Time
    34  	done     bool
    35  }
    36  
    37  // Interaction records details of an in-progress interactive
    38  // macaroon-based login.
    39  type Interaction struct {
    40  	CaveatId   []byte
    41  	LoginUser  names.UserTag
    42  	LoginError error
    43  }
    44  
    45  // NewInteractions returns a new Interactions.
    46  func NewInteractions() *Interactions {
    47  	return &Interactions{
    48  		items: make(map[string]*item),
    49  	}
    50  }
    51  
    52  func newId() (string, error) {
    53  	var id [12]byte
    54  	if _, err := rand.Read(id[:]); err != nil {
    55  		return "", fmt.Errorf("cannot read random id: %v", err)
    56  	}
    57  	return fmt.Sprintf("%x", id[:]), nil
    58  }
    59  
    60  // Start records the start of an interactive login, and returns a random ID
    61  // that uniquely identifies it. A call to Wait with the same ID will return
    62  // the Interaction once it is done.
    63  func (m *Interactions) Start(caveatId []byte, expiry time.Time) (string, error) {
    64  	id, err := newId()
    65  	if err != nil {
    66  		return "", err
    67  	}
    68  	m.mu.Lock()
    69  	defer m.mu.Unlock()
    70  	m.items[id] = &item{
    71  		c:        make(chan Interaction, 1),
    72  		caveatId: caveatId,
    73  		expiry:   expiry,
    74  	}
    75  	return id, nil
    76  }
    77  
    78  // Done signals that the user has either logged in, or attempted to and failed.
    79  func (m *Interactions) Done(id string, loginUser names.UserTag, loginError error) error {
    80  	m.mu.Lock()
    81  	defer m.mu.Unlock()
    82  	item := m.items[id]
    83  
    84  	if item == nil {
    85  		return errors.NotFoundf("interaction %q", id)
    86  	}
    87  	if item.done {
    88  		return errors.Errorf("interaction %q already done", id)
    89  	}
    90  	item.done = true
    91  	item.c <- Interaction{
    92  		CaveatId:   item.caveatId,
    93  		LoginUser:  loginUser,
    94  		LoginError: loginError,
    95  	}
    96  	return nil
    97  }
    98  
    99  // Wait waits until the identified interaction is done, and returns the
   100  // corresponding Interaction. If the cancel channel is signalled before
   101  // the interaction is done, then ErrWaitCanceled is returned. If the
   102  // interaction expires before it is done, ErrExpired is returned.
   103  func (m *Interactions) Wait(id string, cancel <-chan struct{}) (*Interaction, error) {
   104  	m.mu.Lock()
   105  	item := m.items[id]
   106  	m.mu.Unlock()
   107  	if item == nil {
   108  		return nil, errors.NotFoundf("interaction %q", id)
   109  	}
   110  	select {
   111  	case <-cancel:
   112  		return nil, ErrWaitCanceled
   113  	case interaction, ok := <-item.c:
   114  		if !ok {
   115  			return nil, ErrExpired
   116  		}
   117  		m.mu.Lock()
   118  		delete(m.items, id)
   119  		m.mu.Unlock()
   120  		return &interaction, nil
   121  	}
   122  }
   123  
   124  // Expire removes any interactions that were due to expire by the
   125  // specified time.
   126  func (m *Interactions) Expire(t time.Time) {
   127  	m.mu.Lock()
   128  	defer m.mu.Unlock()
   129  	for id, item := range m.items {
   130  		if item.done || item.expiry.After(t) {
   131  			continue
   132  		}
   133  		delete(m.items, id)
   134  		close(item.c)
   135  	}
   136  }