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
41
func GetToken(filler form.Filler, tokenName string) (*usso.SSOData, error) {
42
login, err := filler.Fill(loginForm)
44
return nil, errgo.Notef(err, "cannot read login parameters")
46
tok, err := server.GetTokenWithOTP(
47
login[userKey].(string),
48
login[passKey].(string),
49
login[otpKey].(string),
54
return nil, errgo.Notef(err, "cannot get token")
59
// loginForm contains the fields required for login.
60
var loginForm = form.Form{
61
Fields: environschema.Fields{
62
userKey: environschema.Attr{
63
Description: "Username",
64
Type: environschema.Tstring,
68
passKey: environschema.Attr{
69
Description: "Password",
70
Type: environschema.Tstring,
75
otpKey: environschema.Attr{
76
Description: "Two-factor auth",
77
Type: environschema.Tstring,
84
// LoginWithToken completes a login attempt using tok. The ussoAuthURL
85
// should have been obtained from the UbuntuSSOOAuth field in a response
86
// to a LoginMethods request from the target service.
87
func LoginWithToken(client *http.Client, ussoAuthUrl string, tok *usso.SSOData) error {
88
req, err := http.NewRequest("GET", ussoAuthUrl, nil)
90
return errgo.Notef(err, "cannot create request")
94
rp := usso.RequestParameters{
95
HTTPMethod: req.Method,
96
BaseURL: base.String(),
97
Params: req.URL.Query(),
98
SignatureMethod: usso.HMACSHA1{},
100
if err := tok.SignRequest(&rp, req); err != nil {
101
return errgo.Notef(err, "cannot sign request")
103
resp, err := client.Do(req)
105
return errgo.Notef(err, "cannot do request")
107
defer resp.Body.Close()
108
if resp.StatusCode == http.StatusOK {
111
var herr httpbakery.Error
112
if err := httprequest.UnmarshalJSONResponse(resp, &herr); err != nil {
113
return errgo.Notef(err, "cannot unmarshal error")
118
// TokenStore defines the interface for something that can store and returns oauth tokens.
119
type TokenStore interface {
120
// Put stores an Ubuntu SSO OAuth token.
121
Put(tok *usso.SSOData) error
122
// Get returns an Ubuntu SSO OAuth token from store
123
Get() (*usso.SSOData, error)
126
// FileTokenStore implements the TokenStore interface by storing the
127
// JSON-encoded oauth token in a file.
128
type FileTokenStore struct {
132
// NewFileTokenStore returns a new FileTokenStore
133
// that uses the given path for storage.
134
func NewFileTokenStore(path string) *FileTokenStore {
135
return &FileTokenStore{path}
138
// Put implements TokenStore.Put by writing the token to the
139
// FileTokenStore's file. If the file doesn't exist it will be created,
140
// including any required directories.
141
func (f *FileTokenStore) Put(tok *usso.SSOData) error {
142
data, err := json.Marshal(tok)
144
return errgo.Notef(err, "cannot marshal token")
146
dir := filepath.Dir(f.path)
147
if err := os.MkdirAll(dir, 0700); err != nil {
148
return errgo.Notef(err, "cannot create directory %q", dir)
150
if err := ioutil.WriteFile(f.path, data, 0600); err != nil {
151
return errgo.Notef(err, "cannot write file")
156
// Get implements TokenStore.Get by
157
// reading the token from the FileTokenStore's file.
158
func (f *FileTokenStore) Get() (*usso.SSOData, error) {
159
data, err := ioutil.ReadFile(f.path)
161
return nil, errgo.Notef(err, "cannot read token")
164
if err := json.Unmarshal(data, &tok); err != nil {
165
return nil, errgo.Notef(err, "cannot unmarshal token")