veyron/services/identity: Client side expiry caveat selection improvement.
* Datetime picker is now used on form page.
* cleaned up handling of caveat construction.
Change-Id: If92c2c45fd2362e1bac4a8dfc9600bac15975481
diff --git a/services/identity/googleoauth/handler.go b/services/identity/googleoauth/handler.go
index 2ac127b..fb93bc1 100644
--- a/services/identity/googleoauth/handler.go
+++ b/services/identity/googleoauth/handler.go
@@ -327,6 +327,8 @@
ToolRedirectURL, ToolState, Email string
}
+var caveatList = []string{"ExpiryCaveat", "MethodCaveat"}
+
func (h *handler) addCaveats(w http.ResponseWriter, r *http.Request) {
var inputMacaroon seekBlessingsMacaroon
if err := h.csrfCop.ValidateToken(r.FormValue("state"), r, clientIDCookie, &inputMacaroon); err != nil {
@@ -354,9 +356,9 @@
}
tmplargs := struct {
Extension string
- CaveatMap map[string]caveatInfo
+ CaveatList []string
Macaroon, MacaroonRoute string
- }{email, caveatMap, outputMacaroon, sendMacaroonRoute}
+ }{email, caveatList, outputMacaroon, sendMacaroonRoute}
w.Header().Set("Context-Type", "text/html")
if err := tmplSelectCaveats.Execute(w, tmplargs); err != nil {
vlog.Errorf("Unable to execute bless page template: %v", err)
@@ -416,12 +418,7 @@
// Fill in the required caveat.
switch required := r.FormValue("requiredCaveat"); required {
case "Expiry":
- str := r.FormValue("expiry")
- d, err := time.ParseDuration(str)
- if err != nil {
- return nil, fmt.Errorf("failed to parse expiration duration(%q): %v", str, err)
- }
- expiry, err := security.ExpiryCaveat(time.Now().Add(d))
+ expiry, err := newExpiryCaveat(r.FormValue("expiry"), r.FormValue("timezoneOffset"))
if err != nil {
return nil, fmt.Errorf("failed to create ExpiryCaveat: %v", err)
}
@@ -444,56 +441,45 @@
// And find any additional ones
for i, cavName := range r.Form["caveat"] {
- if cavName == "none" {
+ var err error
+ var caveat security.Caveat
+ switch cavName {
+ case "ExpiryCaveat":
+ caveat, err = newExpiryCaveat(r.Form[cavName][i], r.FormValue("timezoneOffset"))
+ case "MethodCaveat":
+ caveat, err = newMethodCaveat(strings.Split(r.Form[cavName][i], ","))
+ case "none":
continue
- }
- args := strings.Split(r.Form[cavName][i], ",")
- cavInfo, ok := caveatMap[cavName]
- if !ok {
+ default:
return nil, fmt.Errorf("unable to create caveat %s: caveat does not exist", cavName)
}
- caveat, err := cavInfo.New(args...)
if err != nil {
- return nil, fmt.Errorf("unable to create caveat %s(%v): cavInfo.New failed: %v", cavName, args, err)
+ return nil, fmt.Errorf("unable to create caveat %s: %v", cavName, err)
}
caveats = append(caveats, caveat)
}
return caveats, nil
}
-type caveatInfo struct {
- New func(args ...string) (security.Caveat, error)
- Placeholder string
+func newExpiryCaveat(timestamp, utcOffset string) (security.Caveat, error) {
+ var empty security.Caveat
+ t, err := time.Parse("2006-01-02T15:04", timestamp)
+ if err != nil {
+ return empty, fmt.Errorf("parseTime failed: %v", err)
+ }
+ // utcOffset is returned as minutes from JS, so we need to parse it to a duration.
+ offset, err := time.ParseDuration(utcOffset + "m")
+ if err != nil {
+ return empty, fmt.Errorf("failed to parse duration: %v", err)
+ }
+ return security.ExpiryCaveat(t.Add(time.Minute * offset))
}
-// caveatMap is a map from Caveat name to caveat information.
-// To add to this map append
-// key = "CaveatName"
-// New = func that returns instantiation of specific caveat wrapped in security.Caveat.
-// Placeholder = the placeholder text for the html input element.
-var caveatMap = map[string]caveatInfo{
- "ExpiryCaveat": {
- New: func(args ...string) (security.Caveat, error) {
- if len(args) != 1 {
- return security.Caveat{}, fmt.Errorf("must pass exactly one duration string.")
- }
- dur, err := time.ParseDuration(args[0])
- if err != nil {
- return security.Caveat{}, fmt.Errorf("parse duration failed: %v", err)
- }
- return security.ExpiryCaveat(time.Now().Add(dur))
- },
- Placeholder: "i.e. 2h45m. Valid time units are ns, us (or µs), ms, s, m, h.",
- },
- "MethodCaveat": {
- New: func(args ...string) (security.Caveat, error) {
- if len(args) < 1 {
- return security.Caveat{}, fmt.Errorf("must pass at least one method")
- }
- return security.MethodCaveat(args[0], args[1:]...)
- },
- Placeholder: "Comma-separated method names.",
- },
+func newMethodCaveat(methods []string) (security.Caveat, error) {
+ if len(methods) < 1 {
+ return security.Caveat{}, fmt.Errorf("must pass at least one method")
+ }
+ return security.MethodCaveat(methods[0], methods[1:]...)
}
// exchangeAuthCodeForEmail exchanges the authorization code (which must
diff --git a/services/identity/googleoauth/template.go b/services/identity/googleoauth/template.go
index 958c70e..a3dc4ee 100644
--- a/services/identity/googleoauth/template.go
+++ b/services/identity/googleoauth/template.go
@@ -124,7 +124,10 @@
<title>Blessings: Select caveats</title>
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<link rel="stylesheet" href="//netdna.bootstrapcdn.com/bootstrap/3.0.0/css/bootstrap.min.css">
+<link rel="stylesheet" href="//cdnjs.cloudflare.com/ajax/libs/toastr.js/latest/css/toastr.min.css">
+<script src="//cdnjs.cloudflare.com/ajax/libs/moment.js/2.7.0/moment.min.js"></script>
<script src="//ajax.googleapis.com/ajax/libs/jquery/1.11.1/jquery.min.js"></script>
+<script src="//cdnjs.cloudflare.com/ajax/libs/toastr.js/latest/js/toastr.min.js"></script>
<script>
// TODO(suharshs): Move this and other JS/CSS to an assets directory in identity server.
$(document).ready(function() {
@@ -152,19 +155,30 @@
// Upon clicking the '-' button caveats should be removed.
$('body').on('click', '.removeCaveat', function() {
- $(this).parents(".caveatRow").remove();
+ $(this).parents('.caveatRow').remove();
});
+
+ // Get the timezoneOffset for the server to create a correct expiry caveat.
+ // The offset is the minutes between UTC and local time.
+ var d = new Date();
+ $('#timezoneOffset').val(d.getTimezoneOffset());
+
+ // Set the datetime picker to have a default value of one day from now.
+ var m = moment().add(1, 'd').format("YYYY-MM-DDTHH:MM")
+ $('#expiry').val(m);
+ $('#ExpiryCaveat').val(m);
});
</script>
</head>
<body class="container">
-<form class="form-horizontal" method="POST" name="input" action="/google/{{.MacaroonRoute}}" role="form">
+<form class="form-horizontal" method="POST" id="caveats-form" name="input" action="/google/{{.MacaroonRoute}}" role="form">
<h2 class="form-signin-heading">{{.Extension}}</h2>
<input type="text" class="hidden" name="macaroon" value="{{.Macaroon}}">
<div class="form-group form-group-lg">
<label class="col-sm-2" for="blessing-extension">Extension</label>
<div class="col-sm-10">
<input name="blessingExtension" type="text" class="form-control" id="blessing-extension" placeholder="(optional) name of the device/application for which the blessing is being sought, e.g. homelaptop">
+ <input type="text" class="hidden" id="timezoneOffset" name="timezoneOffset">
</div>
</div>
<div class="form-group form-group-lg">
@@ -179,7 +193,7 @@
<div class="radio">
<div class="input-group">
<input type="radio" name="requiredCaveat" id="requiredCaveat" value="Expiry">
- <input type="text" name="expiry" id="expiry" value="1h" placeholder="time after which the blessing will expire">
+ <input type="datetime-local" id="expiry" name="expiry">
</div>
</div>
</div>
@@ -190,15 +204,19 @@
<div class="col-md-4">
<select name="caveat" class="form-control caveats">
<option value="none" selected="selected">Select a caveat.</option>
- {{ $caveatMap := .CaveatMap }}
- {{range $key, $value := $caveatMap}}
- <option name="{{$key}}" value="{{$key}}">{{$key}}</option>
+ {{ $caveatList := .CaveatList }}
+ {{range $index, $name := $caveatList}}
+ <option name="{{$name}}" value="{{$name}}">{{$name}}</option>
{{end}}
</select>
</div>
<div class="col-md-7">
- {{range $key, $entry := $caveatMap}}
- <input type="text" id="{{$key}}" class="form-control caveatInput" name="{{$key}}" placeholder="{{$entry.Placeholder}}">
+ {{range $index, $name := $caveatList}}
+ {{if eq $name "ExpiryCaveat"}}
+ <input type="datetime-local" class="form-control caveatInput" id="{{$name}}" name="{{$name}}">
+ {{else if eq $name "MethodCaveat"}}
+ <input type="text" id="{{$name}}" class="form-control caveatInput" name="{{$name}}" placeholder="comma-separated method list">
+ {{end}}
{{end}}
</div>
<div class="col-md-1">