Documentation
¶
Overview ¶
Package agent enables non-interactive (agent) login using macaroons. To enable agent authorization with a given httpbakery.Client c against a given third party discharge server URL u:
SetUpAuth(c, u, agentUsername)
Index ¶
- Variables
- func LoginCookie(req *http.Request) (username string, key *bakery.PublicKey, err error)
- func SetCookie(jar http.CookieJar, u *url.URL, username string, pk *bakery.PublicKey)
- func SetUpAuth(c *httpbakery.Client, u *url.URL, username string) error
- func VisitWebPage(c *httpbakery.Client) func(u *url.URL) error
- type UnexpectedResponseError
Examples ¶
Constants ¶
This section is empty.
Variables ¶
var ErrNoAgentLoginCookie = errgo.New("no agent-login cookie found")
ErrNoAgentLoginCookie is the error returned when the expected agent login cookie has not been found.
Functions ¶
func LoginCookie ¶
LoginCookie returns details of the agent login cookie from the given request. If no agent-login cookie is found, it returns an ErrNoAgentLoginCookie error.
func SetCookie ¶
SetCookie creates a cookie in jar which is suitable for performing agent logins to u.
If using SetUpAuth, it should not be necessary to use this function.
func SetUpAuth ¶
SetUpAuth configures agent authentication on c. A cookie is created in c's cookie jar containing credentials derived from the username and c.Key. c.VisitWebPage is set to VisitWebPage(c). The return is non-nil only if c.Key is nil.
func VisitWebPage ¶
func VisitWebPage(c *httpbakery.Client) func(u *url.URL) error
VisitWebPage creates a function that can be used with httpbakery.Client.VisitWebPage. The function uses c to access the visit URL. If no agent-login cookie has been configured for u an error with the cause of ErrNoAgentLoginCookie will be returned. If the login fails the returned error will be of type *httpbakery.Error. If the response from the visitURL cannot be interpreted the error will be of type *UnexpectedResponseError.
If using SetUpAuth, it should not be necessary to use this function.
Example ¶
package main
import (
"encoding/base64"
"net/http"
"net/http/httptest"
"net/url"
gc "gopkg.in/check.v1"
"gopkg.in/errgo.v1"
"gopkg.in/macaroon-bakery.v1/bakery"
"gopkg.in/macaroon-bakery.v1/bakery/checkers"
"gopkg.in/macaroon-bakery.v1/httpbakery"
"gopkg.in/macaroon-bakery.v1/httpbakery/agent"
)
type agentSuite struct {
bakery *bakery.Service
dischargeKey *bakery.PublicKey
discharger *Discharger
server *httptest.Server
}
var _ = gc.Suite(&agentSuite{})
func (s *agentSuite) SetUpSuite(c *gc.C) {
key, err := bakery.GenerateKey()
c.Assert(err, gc.IsNil)
s.dischargeKey = &key.Public
c.Assert(err, gc.IsNil)
bak, err := bakery.NewService(bakery.NewServiceParams{
Key: key,
})
c.Assert(err, gc.IsNil)
s.discharger = &Discharger{
Bakery: bak,
}
s.server = s.discharger.Serve()
s.bakery, err = bakery.NewService(bakery.NewServiceParams{
Locator: bakery.PublicKeyLocatorMap{
s.discharger.URL: &key.Public,
},
})
}
func (s *agentSuite) TearDownSuite(c *gc.C) {
s.server.Close()
}
var agentLoginTests = []struct {
about string
loginHandler func(*Discharger, http.ResponseWriter, *http.Request)
expectError string
}{{
about: "success",
}, {
about: "error response",
loginHandler: func(d *Discharger, w http.ResponseWriter, _ *http.Request) {
d.WriteJSON(w, http.StatusBadRequest, httpbakery.Error{
Code: "bad request",
Message: "test error",
})
},
expectError: `cannot get discharge from ".*": cannot start interactive session: test error`,
}, {
about: "unexpected response",
loginHandler: func(d *Discharger, w http.ResponseWriter, _ *http.Request) {
w.Write([]byte("OK"))
},
expectError: `cannot get discharge from ".*": cannot start interactive session: unexpected response to non-interactive web page visit .* \(content type text/plain; charset=utf-8\)`,
}, {
about: "unexpected error response",
loginHandler: func(d *Discharger, w http.ResponseWriter, _ *http.Request) {
d.WriteJSON(w, http.StatusBadRequest, httpbakery.Error{})
},
expectError: `cannot get discharge from ".*": cannot start interactive session: unexpected response to non-interactive web page visit .* \(content type application/json\)`,
}, {
about: "incorrect JSON",
loginHandler: func(d *Discharger, w http.ResponseWriter, _ *http.Request) {
d.WriteJSON(w, http.StatusOK, httpbakery.Error{
Code: "bad request",
Message: "test error",
})
},
expectError: `cannot get discharge from ".*": cannot start interactive session: unexpected response to non-interactive web page visit .* \(content type application/json\)`,
}}
func (s *agentSuite) TestAgentLogin(c *gc.C) {
u, err := url.Parse(s.discharger.URL)
c.Assert(err, gc.IsNil)
for i, test := range agentLoginTests {
c.Logf("%d. %s", i, test.about)
s.discharger.LoginHandler = test.loginHandler
client := httpbakery.NewClient()
client.Key, err = bakery.GenerateKey()
c.Assert(err, gc.IsNil)
err = agent.SetUpAuth(client, u, "test-user")
c.Assert(err, gc.IsNil)
m, err := s.bakery.NewMacaroon("", nil, []checkers.Caveat{{
Location: s.discharger.URL,
Condition: "test condition",
}})
c.Assert(err, gc.IsNil)
ms, err := client.DischargeAll(m)
if test.expectError != "" {
c.Assert(err, gc.ErrorMatches, test.expectError)
continue
}
c.Assert(err, gc.IsNil)
err = s.bakery.Check(ms, bakery.FirstPartyCheckerFunc(
func(caveat string) error {
return nil
},
))
c.Assert(err, gc.IsNil)
}
}
func (s *agentSuite) TestSetUpAuthError(c *gc.C) {
client := httpbakery.NewClient()
err := agent.SetUpAuth(client, nil, "test-user")
c.Assert(err, gc.ErrorMatches, "cannot set-up authentication: client key not configured")
}
func (s *agentSuite) TestNoCookieError(c *gc.C) {
client := httpbakery.NewClient()
client.VisitWebPage = agent.VisitWebPage(client)
m, err := s.bakery.NewMacaroon("", nil, []checkers.Caveat{{
Location: s.discharger.URL,
Condition: "test condition",
}})
c.Assert(err, gc.IsNil)
_, err = client.DischargeAll(m)
c.Assert(err, gc.ErrorMatches, "cannot get discharge from .*: cannot start interactive session: cannot perform agent login: no agent-login cookie found")
ierr := errgo.Cause(err).(*httpbakery.InteractionError)
c.Assert(errgo.Cause(ierr.Reason), gc.Equals, http.ErrNoCookie)
}
func (s *agentSuite) TestLoginCookie(c *gc.C) {
key, err := bakery.GenerateKey()
c.Assert(err, gc.IsNil)
tests := []struct {
about string
setCookie func(*httpbakery.Client, *url.URL)
expectUser string
expectKey *bakery.PublicKey
expectError string
expectCause error
}{{
about: "success",
setCookie: func(client *httpbakery.Client, u *url.URL) {
agent.SetUpAuth(client, u, "bob")
},
expectUser: "bob",
expectKey: &key.Public,
}, {
about: "no cookie",
setCookie: func(client *httpbakery.Client, u *url.URL) {},
expectError: "no agent-login cookie found",
expectCause: agent.ErrNoAgentLoginCookie,
}, {
about: "invalid base64 encoding",
setCookie: func(client *httpbakery.Client, u *url.URL) {
client.Jar.SetCookies(u, []*http.Cookie{{
Name: "agent-login",
Value: "x",
}})
},
expectError: "cannot decode cookie value: illegal base64 data at input byte 0",
}, {
about: "invalid JSON",
setCookie: func(client *httpbakery.Client, u *url.URL) {
client.Jar.SetCookies(u, []*http.Cookie{{
Name: "agent-login",
Value: base64.StdEncoding.EncodeToString([]byte("}")),
}})
},
expectError: "cannot unmarshal agent login: invalid character '}' looking for beginning of value",
}, {
about: "no username",
setCookie: func(client *httpbakery.Client, u *url.URL) {
agent.SetCookie(client.Jar, u, "", &key.Public)
},
expectError: "agent login has no user name",
}, {
about: "no public key",
setCookie: func(client *httpbakery.Client, u *url.URL) {
agent.SetCookie(client.Jar, u, "hello", nil)
},
expectError: "agent login has no public key",
}}
var (
foundUser string
foundKey *bakery.PublicKey
foundErr error
)
srv := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, req *http.Request) {
foundUser, foundKey, foundErr = agent.LoginCookie(req)
}))
defer srv.Close()
srvURL, err := url.Parse(srv.URL)
c.Assert(err, gc.IsNil)
for i, test := range tests {
c.Logf("test %d: %s", i, test.about)
client := httpbakery.NewClient()
client.Key = key
test.setCookie(client, srvURL)
req, err := http.NewRequest("GET", srv.URL, nil)
c.Assert(err, gc.IsNil)
resp, err := client.Do(req)
c.Assert(err, gc.IsNil)
c.Assert(resp.StatusCode, gc.Equals, http.StatusOK)
if test.expectError != "" {
c.Assert(foundErr, gc.ErrorMatches, test.expectError)
if test.expectCause != nil {
c.Assert(errgo.Cause(foundErr), gc.Equals, test.expectCause)
}
continue
}
c.Assert(foundUser, gc.Equals, test.expectUser)
c.Assert(foundKey, gc.DeepEquals, test.expectKey)
}
}
func main() {
var key *bakery.KeyPair
var u *url.URL
client := httpbakery.NewClient()
client.Key = key
agent.SetCookie(client.Jar, u, "agent-username", &client.Key.Public)
client.VisitWebPage = agent.VisitWebPage(client)
}
Output:
Types ¶
type UnexpectedResponseError ¶
UnexpectedResponseError is the error returned when a response is received that cannot be interpreted.
func (*UnexpectedResponseError) Error ¶
func (u *UnexpectedResponseError) Error() string