~nskaggs/+junk/xenial-test

« back to all changes in this revision

Viewing changes to src/github.com/juju/idmclient/ussologin/ussologin.go

  • Committer: Nicholas Skaggs
  • Date: 2016-10-24 20:56:05 UTC
  • Revision ID: nicholas.skaggs@canonical.com-20161024205605-z8lta0uvuhtxwzwl
Initi with beta15

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
// Copyright 2016 Canonical Ltd.
 
2
// Licensed under the LGPLv3, see LICENCE file for details.
 
3
 
 
4
// Package ussologin defines functionality used for allowing clients
 
5
// to authenticate with the IDM server using USSO OAuth.
 
6
package ussologin
 
7
 
 
8
import (
 
9
        "encoding/json"
 
10
        "io/ioutil"
 
11
        "net/http"
 
12
        "os"
 
13
        "path/filepath"
 
14
 
 
15
        "github.com/juju/httprequest"
 
16
        "github.com/juju/usso"
 
17
        "gopkg.in/errgo.v1"
 
18
        "gopkg.in/juju/environschema.v1"
 
19
        "gopkg.in/juju/environschema.v1/form"
 
20
        "gopkg.in/macaroon-bakery.v1/httpbakery"
 
21
)
 
22
 
 
23
type tokenGetter interface {
 
24
        GetTokenWithOTP(username, password, otp, tokenName string) (*usso.SSOData, error)
 
25
}
 
26
 
 
27
// This is defined here to allow it to be stubbed out in tests
 
28
var server tokenGetter = usso.ProductionUbuntuSSOServer
 
29
 
 
30
var (
 
31
        userKey = "E-Mail"
 
32
        passKey = "Password"
 
33
        otpKey  = "Two-factor auth (Enter for none)"
 
34
)
 
35
 
 
36
// GetToken uses filler to interact with the user and uses the provided
 
37
// information to obtain an OAuth token from Ubuntu SSO. The returned
 
38
// token can subsequently be used with LoginWithToken to perform a login.
 
39
// The tokenName argument is used as the name of the generated token in
 
40
// Ubuntu SSO. If Ubuntu SSO returned an error when trying to retrieve
 
41
// the token the error will have a cause of type *usso.Error.
 
42
func GetToken(filler form.Filler, tokenName string) (*usso.SSOData, error) {
 
43
        login, err := filler.Fill(loginForm)
 
44
        if err != nil {
 
45
                return nil, errgo.Notef(err, "cannot read login parameters")
 
46
        }
 
47
        tok, err := server.GetTokenWithOTP(
 
48
                login[userKey].(string),
 
49
                login[passKey].(string),
 
50
                login[otpKey].(string),
 
51
                tokenName,
 
52
        )
 
53
 
 
54
        if err != nil {
 
55
                return nil, errgo.NoteMask(err, "cannot get token", isUSSOError)
 
56
        }
 
57
        return tok, nil
 
58
}
 
59
 
 
60
// loginForm contains the fields required for login.
 
61
var loginForm = form.Form{
 
62
        Title: "Login to Ubuntu SSO",
 
63
        Fields: environschema.Fields{
 
64
                userKey: environschema.Attr{
 
65
                        Description: "Username",
 
66
                        Type:        environschema.Tstring,
 
67
                        Mandatory:   true,
 
68
                        Group:       "1",
 
69
                },
 
70
                passKey: environschema.Attr{
 
71
                        Description: "Password",
 
72
                        Type:        environschema.Tstring,
 
73
                        Mandatory:   true,
 
74
                        Secret:      true,
 
75
                        Group:       "1",
 
76
                },
 
77
                otpKey: environschema.Attr{
 
78
                        Description: "Two-factor auth",
 
79
                        Type:        environschema.Tstring,
 
80
                        Mandatory:   true,
 
81
                        Group:       "2",
 
82
                },
 
83
        },
 
84
}
 
85
 
 
86
// LoginWithToken completes a login attempt using tok. The ussoAuthURL
 
87
// should have been obtained from the UbuntuSSOOAuth field in a response
 
88
// to a LoginMethods request from the target service.
 
89
func LoginWithToken(client *http.Client, ussoAuthUrl string, tok *usso.SSOData) error {
 
90
        req, err := http.NewRequest("GET", ussoAuthUrl, nil)
 
91
        if err != nil {
 
92
                return errgo.Notef(err, "cannot create request")
 
93
        }
 
94
        base := *req.URL
 
95
        base.RawQuery = ""
 
96
        rp := usso.RequestParameters{
 
97
                HTTPMethod:      req.Method,
 
98
                BaseURL:         base.String(),
 
99
                Params:          req.URL.Query(),
 
100
                SignatureMethod: usso.HMACSHA1{},
 
101
        }
 
102
        if err := tok.SignRequest(&rp, req); err != nil {
 
103
                return errgo.Notef(err, "cannot sign request")
 
104
        }
 
105
        resp, err := client.Do(req)
 
106
        if err != nil {
 
107
                return errgo.Notef(err, "cannot do request")
 
108
        }
 
109
        defer resp.Body.Close()
 
110
        if resp.StatusCode == http.StatusOK {
 
111
                return nil
 
112
        }
 
113
        var herr httpbakery.Error
 
114
        if err := httprequest.UnmarshalJSONResponse(resp, &herr); err != nil {
 
115
                return errgo.Notef(err, "cannot unmarshal error")
 
116
        }
 
117
        return &herr
 
118
}
 
119
 
 
120
// TokenStore defines the interface for something that can store and returns oauth tokens.
 
121
type TokenStore interface {
 
122
        // Put stores an Ubuntu SSO OAuth token.
 
123
        Put(tok *usso.SSOData) error
 
124
        // Get returns an Ubuntu SSO OAuth token from store
 
125
        Get() (*usso.SSOData, error)
 
126
}
 
127
 
 
128
// FileTokenStore implements the TokenStore interface by storing the
 
129
// JSON-encoded oauth token in a file.
 
130
type FileTokenStore struct {
 
131
        path string
 
132
}
 
133
 
 
134
// NewFileTokenStore returns a new FileTokenStore
 
135
// that uses the given path for storage.
 
136
func NewFileTokenStore(path string) *FileTokenStore {
 
137
        return &FileTokenStore{path}
 
138
}
 
139
 
 
140
// Put implements TokenStore.Put by writing the token to the
 
141
// FileTokenStore's file. If the file doesn't exist it will be created,
 
142
// including any required directories.
 
143
func (f *FileTokenStore) Put(tok *usso.SSOData) error {
 
144
        data, err := json.Marshal(tok)
 
145
        if err != nil {
 
146
                return errgo.Notef(err, "cannot marshal token")
 
147
        }
 
148
        dir := filepath.Dir(f.path)
 
149
        if err := os.MkdirAll(dir, 0700); err != nil {
 
150
                return errgo.Notef(err, "cannot create directory %q", dir)
 
151
        }
 
152
        if err := ioutil.WriteFile(f.path, data, 0600); err != nil {
 
153
                return errgo.Notef(err, "cannot write file")
 
154
        }
 
155
        return nil
 
156
}
 
157
 
 
158
// Get implements TokenStore.Get by
 
159
// reading the token from the FileTokenStore's file.
 
160
func (f *FileTokenStore) Get() (*usso.SSOData, error) {
 
161
        data, err := ioutil.ReadFile(f.path)
 
162
        if err != nil {
 
163
                return nil, errgo.Notef(err, "cannot read token")
 
164
        }
 
165
        var tok usso.SSOData
 
166
        if err := json.Unmarshal(data, &tok); err != nil {
 
167
                return nil, errgo.Notef(err, "cannot unmarshal token")
 
168
        }
 
169
        return &tok, nil
 
170
}
 
171
 
 
172
// isUSSOError determines if err represents an error of type *usso.Error.
 
173
func isUSSOError(err error) bool {
 
174
        _, ok := err.(*usso.Error)
 
175
        return ok
 
176
}