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 }