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

     1  // Copyright 2018 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package httpcontext
     5  
     6  import (
     7  	"context"
     8  	"encoding/json"
     9  	"fmt"
    10  	"net/http"
    11  	"time"
    12  
    13  	"github.com/juju/errors"
    14  	"github.com/juju/loggo"
    15  	"gopkg.in/juju/names.v2"
    16  	"gopkg.in/macaroon.v2-unstable"
    17  
    18  	"github.com/juju/juju/apiserver/common"
    19  	"github.com/juju/juju/apiserver/params"
    20  )
    21  
    22  var logger = loggo.GetLogger("juju.apiserver.httpcontext")
    23  
    24  // LocalMacaroonAuthenticator extends Authenticator with a method of
    25  // creating a local login macaroon. The authenticator is expected to
    26  // honour the resulting macaroon.
    27  type LocalMacaroonAuthenticator interface {
    28  	Authenticator
    29  
    30  	// CreateLocalLoginMacaroon creates a macaroon that may be
    31  	// provided to a user as proof that they have logged in with
    32  	// a valid username and password. This macaroon may then be
    33  	// used to obtain a discharge macaroon so that the user can
    34  	// log in without presenting their password for a set amount
    35  	// of time.
    36  	CreateLocalLoginMacaroon(names.UserTag) (*macaroon.Macaroon, error)
    37  }
    38  
    39  // Authenticator provides an interface for authenticating a request.
    40  //
    41  // TODO(axw) contract should include macaroon discharge error.
    42  //
    43  // If this returns an error, the handler should return StatusUnauthorized.
    44  type Authenticator interface {
    45  	// Authenticate authenticates the given request, returning the
    46  	// auth info.
    47  	//
    48  	// If the request does not contain any authentication details,
    49  	// then an error satisfying errors.IsNotFound will be returned.
    50  	Authenticate(req *http.Request) (AuthInfo, error)
    51  
    52  	// AuthenticateLoginRequest authenticates a LoginRequest.
    53  	//
    54  	// TODO(axw) we shouldn't be using params types here.
    55  	AuthenticateLoginRequest(
    56  		serverHost string,
    57  		modelUUID string,
    58  		req params.LoginRequest,
    59  	) (AuthInfo, error)
    60  }
    61  
    62  // Authorizer is a function type for authorizing a request.
    63  //
    64  // If this returns an error, the handler should return StatusForbidden.
    65  type Authorizer interface {
    66  	Authorize(AuthInfo) error
    67  }
    68  
    69  // AuthorizerFunc is a function type implementing Authorizer.
    70  type AuthorizerFunc func(AuthInfo) error
    71  
    72  // Authorize is part of the Authorizer interface.
    73  func (f AuthorizerFunc) Authorize(info AuthInfo) error {
    74  	return f(info)
    75  }
    76  
    77  // Entity represents a user, machine, or unit that might be
    78  // authenticated.
    79  type Entity interface {
    80  	Tag() names.Tag
    81  }
    82  
    83  // AuthInfo is returned by Authenticator and RequestAuthInfo.
    84  type AuthInfo struct {
    85  	// Entity is the user/machine/unit/etc that has authenticated.
    86  	Entity Entity
    87  
    88  	// LastConnection returns the time of the last connection for
    89  	// the authenticated entity. If it's the zero value, then the
    90  	// entity has not previously logged in.
    91  	LastConnection time.Time
    92  
    93  	// Controller reports whether or not the authenticated
    94  	// entity is a controller agent.
    95  	Controller bool
    96  }
    97  
    98  // BasicAuthHandler is an http.Handler that authenticates requests that
    99  // it handles with a specified Authenticator. The auth details can later
   100  // be retrieved using the top-level RequestAuthInfo function in this package.
   101  type BasicAuthHandler struct {
   102  	http.Handler
   103  
   104  	// Authenticator is the Authenticator used for authenticating
   105  	// the HTTP requests handled by this handler.
   106  	Authenticator Authenticator
   107  
   108  	// Authorizer, if non-nil, will be called with the auth info
   109  	// returned by the Authenticator, to validate it for the route.
   110  	Authorizer Authorizer
   111  }
   112  
   113  // sendStatusAndJSON sends an HTTP status code and
   114  // a JSON-encoded response to a client.
   115  func sendStatusAndJSON(w http.ResponseWriter, statusCode int, response interface{}) error {
   116  	body, err := json.Marshal(response)
   117  	if err != nil {
   118  		return errors.Errorf("cannot marshal JSON result %#v: %v", response, err)
   119  	}
   120  
   121  	if statusCode == http.StatusUnauthorized {
   122  		w.Header().Set("WWW-Authenticate", `Basic realm="juju"`)
   123  	}
   124  	w.Header().Set("Content-Type", params.ContentTypeJSON)
   125  	w.Header().Set("Content-Length", fmt.Sprint(len(body)))
   126  	w.WriteHeader(statusCode)
   127  	if _, err := w.Write(body); err != nil {
   128  		return errors.Annotate(err, "cannot write response")
   129  	}
   130  	return nil
   131  }
   132  
   133  // sendError sends a JSON-encoded error response
   134  // for errors encountered during processing.
   135  func sendError(w http.ResponseWriter, errToSend error) error {
   136  	paramsErr, statusCode := common.ServerErrorAndStatus(errToSend)
   137  	logger.Debugf("sending error: %d %v", statusCode, paramsErr)
   138  	return errors.Trace(sendStatusAndJSON(w, statusCode, &params.ErrorResult{
   139  		Error: paramsErr,
   140  	}))
   141  }
   142  
   143  // ServeHTTP is part of the http.Handler interface.
   144  func (h *BasicAuthHandler) ServeHTTP(w http.ResponseWriter, req *http.Request) {
   145  	authInfo, err := h.Authenticator.Authenticate(req)
   146  	if err != nil {
   147  		w.Header().Set("WWW-Authenticate", `Basic realm="juju"`)
   148  		if common.IsDischargeRequiredError(err) {
   149  			sendErr := sendError(w, err)
   150  			if sendErr != nil {
   151  				logger.Errorf("%v", sendErr)
   152  			}
   153  			return
   154  		}
   155  		http.Error(w,
   156  			fmt.Sprintf("authentication failed: %s", err),
   157  			http.StatusUnauthorized,
   158  		)
   159  		return
   160  	}
   161  	if h.Authorizer != nil {
   162  		if err := h.Authorizer.Authorize(authInfo); err != nil {
   163  			http.Error(w,
   164  				fmt.Sprintf("authorization failed: %s", err),
   165  				http.StatusForbidden,
   166  			)
   167  			return
   168  		}
   169  	}
   170  	ctx := context.WithValue(req.Context(), authInfoKey{}, authInfo)
   171  	req = req.WithContext(ctx)
   172  	h.Handler.ServeHTTP(w, req)
   173  }
   174  
   175  type authInfoKey struct{}
   176  
   177  // RequestAuthInfo returns the AuthInfo associated with the request,
   178  // if any, and a boolean indicating whether or not the request was
   179  // authenticated.
   180  func RequestAuthInfo(req *http.Request) (AuthInfo, bool) {
   181  	authInfo, ok := req.Context().Value(authInfoKey{}).(AuthInfo)
   182  	return authInfo, ok
   183  }