github.com/wallyworld/juju@v0.0.0-20161013125918-6cf1bc9d917a/apiserver/locallogin.go (about)

     1  // Copyright 2016 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package apiserver
     5  
     6  import (
     7  	"net/http"
     8  
     9  	"github.com/juju/errors"
    10  	"github.com/juju/httprequest"
    11  	"github.com/julienschmidt/httprouter"
    12  	"gopkg.in/juju/names.v2"
    13  	"gopkg.in/macaroon-bakery.v1/bakery"
    14  	"gopkg.in/macaroon-bakery.v1/bakery/checkers"
    15  	"gopkg.in/macaroon-bakery.v1/httpbakery"
    16  	macaroon "gopkg.in/macaroon.v1"
    17  
    18  	"github.com/juju/juju/apiserver/authentication"
    19  	"github.com/juju/juju/apiserver/params"
    20  	"github.com/juju/juju/state"
    21  )
    22  
    23  var (
    24  	errorMapper httprequest.ErrorMapper = httpbakery.ErrorToResponse
    25  	handleJSON                          = errorMapper.HandleJSON
    26  )
    27  
    28  func makeHandler(h httprouter.Handle) http.Handler {
    29  	return http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
    30  		h(w, req, nil)
    31  	})
    32  }
    33  
    34  type localLoginHandlers struct {
    35  	authCtxt *authContext
    36  	state    *state.State
    37  }
    38  
    39  func (h *localLoginHandlers) serveLogin(p httprequest.Params) (interface{}, error) {
    40  	switch p.Request.Method {
    41  	case "POST":
    42  		return h.serveLoginPost(p)
    43  	case "GET":
    44  		return h.serveLoginGet(p)
    45  	default:
    46  		return nil, errors.Errorf("unsupported method %q", p.Request.Method)
    47  	}
    48  }
    49  
    50  func (h *localLoginHandlers) serveLoginPost(p httprequest.Params) (interface{}, error) {
    51  	if err := p.Request.ParseForm(); err != nil {
    52  		return nil, err
    53  	}
    54  	waitId := p.Request.Form.Get("waitid")
    55  	if waitId == "" {
    56  		return nil, errors.NotValidf("missing waitid")
    57  	}
    58  	username := p.Request.Form.Get("user")
    59  	password := p.Request.Form.Get("password")
    60  	if !names.IsValidUser(username) {
    61  		return nil, errors.NotValidf("username %q", username)
    62  	}
    63  	userTag := names.NewUserTag(username)
    64  	if !userTag.IsLocal() {
    65  		return nil, errors.NotValidf("non-local username %q", username)
    66  	}
    67  
    68  	authenticator := h.authCtxt.authenticator(p.Request.Host)
    69  	if _, err := authenticator.Authenticate(h.state, userTag, params.LoginRequest{
    70  		Credentials: password,
    71  	}); err != nil {
    72  		// Mark the interaction as done (but failed),
    73  		// unblocking a pending "/auth/wait" request.
    74  		if err := h.authCtxt.localUserInteractions.Done(waitId, userTag, err); err != nil {
    75  			if !errors.IsNotFound(err) {
    76  				logger.Warningf(
    77  					"failed to record completion of interaction %q for %q",
    78  					waitId, userTag.Id(),
    79  				)
    80  			}
    81  		}
    82  		return nil, errors.Trace(err)
    83  	}
    84  
    85  	// Provide the client with a macaroon that they can use to
    86  	// prove that they have logged in, and obtain a discharge
    87  	// macaroon.
    88  	m, err := h.authCtxt.CreateLocalLoginMacaroon(userTag)
    89  	if err != nil {
    90  		return nil, err
    91  	}
    92  	cookie, err := httpbakery.NewCookie(macaroon.Slice{m})
    93  	if err != nil {
    94  		return nil, err
    95  	}
    96  	http.SetCookie(p.Response, cookie)
    97  
    98  	// Mark the interaction as done, unblocking a pending
    99  	// "/auth/wait" request.
   100  	if err := h.authCtxt.localUserInteractions.Done(
   101  		waitId, userTag, nil,
   102  	); err != nil {
   103  		if errors.IsNotFound(err) {
   104  			err = errors.New("login timed out")
   105  		}
   106  		return nil, err
   107  	}
   108  	return nil, nil
   109  }
   110  
   111  func (h *localLoginHandlers) serveLoginGet(p httprequest.Params) (interface{}, error) {
   112  	if p.Request.Header.Get("Accept") == "application/json" {
   113  		// The application/json content-type is used to
   114  		// inform the client of the supported auth methods.
   115  		return map[string]string{
   116  			"juju_userpass": p.Request.URL.String(),
   117  		}, nil
   118  	}
   119  	// TODO(axw) return an HTML form. If waitid is supplied,
   120  	// it should be passed through so we can unblock a request
   121  	// on the /auth/wait endpoint. We should also support logging
   122  	// in when not specifically directed to the login page.
   123  	return nil, errors.NotImplementedf("GET")
   124  }
   125  
   126  func (h *localLoginHandlers) serveWait(p httprequest.Params) (interface{}, error) {
   127  	if err := p.Request.ParseForm(); err != nil {
   128  		return nil, err
   129  	}
   130  	if p.Request.Method != "GET" {
   131  		return nil, errors.Errorf("unsupported method %q", p.Request.Method)
   132  	}
   133  	waitId := p.Request.Form.Get("waitid")
   134  	if waitId == "" {
   135  		return nil, errors.NotValidf("missing waitid")
   136  	}
   137  	interaction, err := h.authCtxt.localUserInteractions.Wait(waitId, nil)
   138  	if err != nil {
   139  		return nil, errors.Trace(err)
   140  	}
   141  	if interaction.LoginError != nil {
   142  		return nil, errors.Trace(err)
   143  	}
   144  	ctx := macaroonAuthContext{
   145  		authContext: h.authCtxt,
   146  		req:         p.Request,
   147  	}
   148  	macaroon, err := h.authCtxt.localUserThirdPartyBakeryService.Discharge(
   149  		&ctx, interaction.CaveatId,
   150  	)
   151  	if err != nil {
   152  		return nil, errors.Annotate(err, "discharging macaroon")
   153  	}
   154  	return httpbakery.WaitResponse{macaroon}, nil
   155  }
   156  
   157  func (h *localLoginHandlers) checkThirdPartyCaveat(req *http.Request, cavId, cav string) ([]checkers.Caveat, error) {
   158  	ctx := &macaroonAuthContext{authContext: h.authCtxt, req: req}
   159  	return ctx.CheckThirdPartyCaveat(cavId, cav)
   160  }
   161  
   162  type macaroonAuthContext struct {
   163  	*authContext
   164  	req *http.Request
   165  }
   166  
   167  // CheckThirdPartyCaveat is part of the bakery.ThirdPartyChecker interface.
   168  func (ctx *macaroonAuthContext) CheckThirdPartyCaveat(cavId, cav string) ([]checkers.Caveat, error) {
   169  	tag, err := ctx.CheckLocalLoginCaveat(cav)
   170  	if err != nil {
   171  		return nil, errors.Trace(err)
   172  	}
   173  	firstPartyCaveats, err := ctx.CheckLocalLoginRequest(ctx.req, tag)
   174  	if err != nil {
   175  		if _, ok := errors.Cause(err).(*bakery.VerificationError); ok {
   176  			waitId, err := ctx.localUserInteractions.Start(
   177  				cavId,
   178  				ctx.clock.Now().Add(authentication.LocalLoginInteractionTimeout),
   179  			)
   180  			if err != nil {
   181  				return nil, errors.Trace(err)
   182  			}
   183  			visitURL := localUserIdentityLocationPath + "/login?waitid=" + waitId
   184  			waitURL := localUserIdentityLocationPath + "/wait?waitid=" + waitId
   185  			return nil, httpbakery.NewInteractionRequiredError(visitURL, waitURL, nil, ctx.req)
   186  		}
   187  		return nil, errors.Trace(err)
   188  	}
   189  	return firstPartyCaveats, nil
   190  }