~juju-qa/ubuntu/xenial/juju/xenial-2.0-beta3

« back to all changes in this revision

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

  • Committer: Martin Packman
  • Date: 2016-03-30 19:31:08 UTC
  • mfrom: (1.1.41)
  • Revision ID: martin.packman@canonical.com-20160330193108-h9iz3ak334uk0z5r
Merge new upstream source 2.0~beta3

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 = "Username"
 
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.
 
41
func GetToken(filler form.Filler, tokenName string) (*usso.SSOData, error) {
 
42
        login, err := filler.Fill(loginForm)
 
43
        if err != nil {
 
44
                return nil, errgo.Notef(err, "cannot read login parameters")
 
45
        }
 
46
        tok, err := server.GetTokenWithOTP(
 
47
                login[userKey].(string),
 
48
                login[passKey].(string),
 
49
                login[otpKey].(string),
 
50
                tokenName,
 
51
        )
 
52
 
 
53
        if err != nil {
 
54
                return nil, errgo.Notef(err, "cannot get token")
 
55
        }
 
56
        return tok, nil
 
57
}
 
58
 
 
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,
 
65
                        Mandatory:   true,
 
66
                        Group:       "1",
 
67
                },
 
68
                passKey: environschema.Attr{
 
69
                        Description: "Password",
 
70
                        Type:        environschema.Tstring,
 
71
                        Mandatory:   true,
 
72
                        Secret:      true,
 
73
                        Group:       "1",
 
74
                },
 
75
                otpKey: environschema.Attr{
 
76
                        Description: "Two-factor auth",
 
77
                        Type:        environschema.Tstring,
 
78
                        Mandatory:   true,
 
79
                        Group:       "2",
 
80
                },
 
81
        },
 
82
}
 
83
 
 
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)
 
89
        if err != nil {
 
90
                return errgo.Notef(err, "cannot create request")
 
91
        }
 
92
        base := *req.URL
 
93
        base.RawQuery = ""
 
94
        rp := usso.RequestParameters{
 
95
                HTTPMethod:      req.Method,
 
96
                BaseURL:         base.String(),
 
97
                Params:          req.URL.Query(),
 
98
                SignatureMethod: usso.HMACSHA1{},
 
99
        }
 
100
        if err := tok.SignRequest(&rp, req); err != nil {
 
101
                return errgo.Notef(err, "cannot sign request")
 
102
        }
 
103
        resp, err := client.Do(req)
 
104
        if err != nil {
 
105
                return errgo.Notef(err, "cannot do request")
 
106
        }
 
107
        defer resp.Body.Close()
 
108
        if resp.StatusCode == http.StatusOK {
 
109
                return nil
 
110
        }
 
111
        var herr httpbakery.Error
 
112
        if err := httprequest.UnmarshalJSONResponse(resp, &herr); err != nil {
 
113
                return errgo.Notef(err, "cannot unmarshal error")
 
114
        }
 
115
        return &herr
 
116
}
 
117
 
 
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)
 
124
}
 
125
 
 
126
// FileTokenStore implements the TokenStore interface by storing the
 
127
// JSON-encoded oauth token in a file.
 
128
type FileTokenStore struct {
 
129
        path string
 
130
}
 
131
 
 
132
// NewFileTokenStore returns a new FileTokenStore
 
133
// that uses the given path for storage.
 
134
func NewFileTokenStore(path string) *FileTokenStore {
 
135
        return &FileTokenStore{path}
 
136
}
 
137
 
 
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)
 
143
        if err != nil {
 
144
                return errgo.Notef(err, "cannot marshal token")
 
145
        }
 
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)
 
149
        }
 
150
        if err := ioutil.WriteFile(f.path, data, 0600); err != nil {
 
151
                return errgo.Notef(err, "cannot write file")
 
152
        }
 
153
        return nil
 
154
}
 
155
 
 
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)
 
160
        if err != nil {
 
161
                return nil, errgo.Notef(err, "cannot read token")
 
162
        }
 
163
        var tok usso.SSOData
 
164
        if err := json.Unmarshal(data, &tok); err != nil {
 
165
                return nil, errgo.Notef(err, "cannot unmarshal token")
 
166
        }
 
167
        return &tok, nil
 
168
}