1
// Copyright 2016 Canonical Ltd.
2
// Licensed under the LGPLv3, see LICENCE file for details.
4
// Package ussologin defines functionality used for allowing clients
5
// to authenticate with the IDM server using USSO OAuth.
15
"github.com/juju/httprequest"
16
"github.com/juju/usso"
18
"gopkg.in/juju/environschema.v1"
19
"gopkg.in/juju/environschema.v1/form"
20
"gopkg.in/macaroon-bakery.v1/httpbakery"
23
type tokenGetter interface {
24
GetTokenWithOTP(username, password, otp, tokenName string) (*usso.SSOData, error)
27
// This is defined here to allow it to be stubbed out in tests
28
var server tokenGetter = usso.ProductionUbuntuSSOServer
33
otpKey = "Two-factor auth (Enter for none)"
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)
45
return nil, errgo.Notef(err, "cannot read login parameters")
47
tok, err := server.GetTokenWithOTP(
48
login[userKey].(string),
49
login[passKey].(string),
50
login[otpKey].(string),
55
return nil, errgo.NoteMask(err, "cannot get token", isUSSOError)
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,
70
passKey: environschema.Attr{
71
Description: "Password",
72
Type: environschema.Tstring,
77
otpKey: environschema.Attr{
78
Description: "Two-factor auth",
79
Type: environschema.Tstring,
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)
92
return errgo.Notef(err, "cannot create request")
96
rp := usso.RequestParameters{
97
HTTPMethod: req.Method,
98
BaseURL: base.String(),
99
Params: req.URL.Query(),
100
SignatureMethod: usso.HMACSHA1{},
102
if err := tok.SignRequest(&rp, req); err != nil {
103
return errgo.Notef(err, "cannot sign request")
105
resp, err := client.Do(req)
107
return errgo.Notef(err, "cannot do request")
109
defer resp.Body.Close()
110
if resp.StatusCode == http.StatusOK {
113
var herr httpbakery.Error
114
if err := httprequest.UnmarshalJSONResponse(resp, &herr); err != nil {
115
return errgo.Notef(err, "cannot unmarshal error")
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)
128
// FileTokenStore implements the TokenStore interface by storing the
129
// JSON-encoded oauth token in a file.
130
type FileTokenStore struct {
134
// NewFileTokenStore returns a new FileTokenStore
135
// that uses the given path for storage.
136
func NewFileTokenStore(path string) *FileTokenStore {
137
return &FileTokenStore{path}
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)
146
return errgo.Notef(err, "cannot marshal token")
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)
152
if err := ioutil.WriteFile(f.path, data, 0600); err != nil {
153
return errgo.Notef(err, "cannot write file")
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)
163
return nil, errgo.Notef(err, "cannot read token")
166
if err := json.Unmarshal(data, &tok); err != nil {
167
return nil, errgo.Notef(err, "cannot unmarshal token")
172
// isUSSOError determines if err represents an error of type *usso.Error.
173
func isUSSOError(err error) bool {
174
_, ok := err.(*usso.Error)