This repository has been archived by the owner on Nov 8, 2017. It is now read-only.
forked from go-macaroon-bakery/macaroon-bakery
-
Notifications
You must be signed in to change notification settings - Fork 0
/
form.go
142 lines (126 loc) · 4.36 KB
/
form.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
// Package form enables interactive login without using a web browser.
package form
import (
"net/url"
"github.com/juju/httprequest"
"github.com/juju/loggo"
"golang.org/x/net/publicsuffix"
"gopkg.in/errgo.v1"
"gopkg.in/juju/environschema.v1"
"gopkg.in/juju/environschema.v1/form"
"github.com/flynn/macaroon-bakery/httpbakery"
)
var logger = loggo.GetLogger("httpbakery.form")
/*
PROTOCOL
A form login works as follows:
Client Login Service
| |
| GET visitURL with |
| "Accept: application/json" |
|----------------------------------->|
| |
| Login Methods (including "form") |
|<-----------------------------------|
| |
| GET "form" URL |
|----------------------------------->|
| |
| Schema definition |
|<-----------------------------------|
| |
+-------------+ |
| Client | |
| Interaction | |
+-------------+ |
| |
| POST data to "form" URL |
|----------------------------------->|
| |
| Form login response |
|<-----------------------------------|
| |
The schema is provided as a environschema.Fileds object. It is the
client's responsibility to interpret the schema and present it to the
user.
*/
const (
// InteractionMethod is the methodURLs key
// used for a URL that can be used for form-based
// interaction.
InteractionMethod = "form"
)
// SchemaRequest is a request for a form schema.
type SchemaRequest struct {
httprequest.Route `httprequest:"GET"`
}
// SchemaResponse contains the message expected in response to the schema
// request.
type SchemaResponse struct {
Schema environschema.Fields `json:"schema"`
}
// LoginRequest is a request to perform a login using the provided form.
type LoginRequest struct {
httprequest.Route `httprequest:"POST"`
Body LoginBody `httprequest:",body"`
}
// LoginBody holds the body of a form login request.
type LoginBody struct {
Form map[string]interface{} `json:"form"`
}
// Visitor implements httpbakery.Visitor
// by providing form-based interaction.
type Visitor struct {
// Filler holds the form filler that will be used when
// form-based interaction is required.
Filler form.Filler
}
// visitWebPage performs the actual visit request. It attempts to
// determine that form login is supported and then download the form
// schema. It calls v.handler.Handle using the downloaded schema and then
// submits the returned form. Any error produced by v.handler.Handle will
// not have it's cause masked.
func (v Visitor) VisitWebPage(client *httpbakery.Client, methodURLs map[string]*url.URL) error {
return v.visitWebPage(client, methodURLs)
}
// visitWebPage is the internal version of VisitWebPage that operates
// on a Doer rather than an httpbakery.Client, so that we
// can remain compatible with the historic
// signature of the VisitWebPage function.
func (v Visitor) visitWebPage(doer httprequest.Doer, methodURLs map[string]*url.URL) error {
schemaURL := methodURLs[InteractionMethod]
if schemaURL == nil {
return httpbakery.ErrMethodNotSupported
}
logger.Infof("got schemaURL %v", schemaURL)
httpReqClient := &httprequest.Client{
Doer: doer,
}
var s SchemaResponse
if err := httpReqClient.CallURL(schemaURL.String(), &SchemaRequest{}, &s); err != nil {
return errgo.Notef(err, "cannot get schema")
}
if len(s.Schema) == 0 {
return errgo.Newf("invalid schema: no fields found")
}
host, err := publicsuffix.EffectiveTLDPlusOne(schemaURL.Host)
if err != nil {
host = schemaURL.Host
}
form, err := v.Filler.Fill(form.Form{
Title: "Log in to " + host,
Fields: s.Schema,
})
if err != nil {
return errgo.NoteMask(err, "cannot handle form", errgo.Any)
}
lr := LoginRequest{
Body: LoginBody{
Form: form,
},
}
if err := httpReqClient.CallURL(schemaURL.String(), &lr, nil); err != nil {
return errgo.Notef(err, "cannot submit form")
}
return nil
}