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

« back to all changes in this revision

Viewing changes to src/github.com/juju/usso/usso.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 2015 Canonical Ltd.
 
2
// Licensed under the LGPLv3, see LICENSE file for details.
 
3
 
 
4
package usso
 
5
 
 
6
import (
 
7
        "encoding/json"
 
8
        "fmt"
 
9
        "io/ioutil"
 
10
        "net/http"
 
11
        "strings"
 
12
)
 
13
 
 
14
type UbuntuSSOServer struct {
 
15
        baseUrl              string
 
16
        tokenRegistrationUrl string
 
17
}
 
18
 
 
19
// tokenURL returns the URL where the Ubuntu SSO tokens can be requested.
 
20
func (server UbuntuSSOServer) tokenURL() string {
 
21
        return server.baseUrl + "/api/v2/tokens/oauth"
 
22
}
 
23
 
 
24
// AccountURL returns the URL where the Ubuntu SSO account information can be
 
25
// requested.
 
26
func (server UbuntuSSOServer) AccountsURL() string {
 
27
        return server.baseUrl + "/api/v2/accounts/"
 
28
}
 
29
 
 
30
// TokenDetailURL returns the URL where the Ubuntu SSO token details can be
 
31
// requested.
 
32
func (server UbuntuSSOServer) TokenDetailsURL() string {
 
33
        return server.baseUrl + "/api/v2/tokens/oauth/"
 
34
}
 
35
 
 
36
// LoginURL returns the url for Openid login
 
37
func (server UbuntuSSOServer) LoginURL() string {
 
38
        return server.baseUrl
 
39
}
 
40
 
 
41
// ProductionUbuntuSSOServer represents the production Ubuntu SSO server
 
42
// located at https://login.ubuntu.com.
 
43
var ProductionUbuntuSSOServer = UbuntuSSOServer{"https://login.ubuntu.com", "https://one.ubuntu.com/oauth/sso-finished-so-get-tokens/"}
 
44
 
 
45
// StagingUbuntuSSOServer represents the staging Ubuntu SSO server located
 
46
// at https://login.staging.ubuntu.com. Use it for testing.
 
47
var StagingUbuntuSSOServer = UbuntuSSOServer{"https://login.staging.ubuntu.com", "https://one.staging.ubuntu.com/oauth/sso-finished-so-get-tokens/"}
 
48
 
 
49
// Giving user credentials and token name, retrieves oauth credentials
 
50
// for the users, the oauth credentials can be used later to sign requests.
 
51
func (server UbuntuSSOServer) GetToken(email string, password string, tokenName string) (*SSOData, error) {
 
52
        return server.GetTokenWithOTP(email, password, "", tokenName)
 
53
}
 
54
 
 
55
// GetTokenWithOTP retrieves an oauth token from the Ubuntu SSO server.
 
56
// Using the user credentials including two-factor authentication and the
 
57
// token name, an oauth token is retrieved that can later be used to sign
 
58
// requests. If otp is blank then this is identical to GetToken.
 
59
func (server UbuntuSSOServer) GetTokenWithOTP(email, password, otp, tokenName string) (*SSOData, error) {
 
60
        credentials := map[string]string{
 
61
                "email":      email,
 
62
                "password":   password,
 
63
                "token_name": tokenName,
 
64
        }
 
65
        if otp != "" {
 
66
                credentials["otp"] = otp
 
67
        }
 
68
        jsonCredentials, err := json.Marshal(credentials)
 
69
        if err != nil {
 
70
                return nil, err
 
71
        }
 
72
        response, err := http.Post(
 
73
                server.tokenURL(),
 
74
                "application/json",
 
75
                strings.NewReader(string(jsonCredentials)))
 
76
        if err != nil {
 
77
                return nil, err
 
78
        }
 
79
        if response.StatusCode == 404 {
 
80
                return nil, fmt.Errorf("Wrong credentials.")
 
81
        }
 
82
        if response.StatusCode != 200 && response.StatusCode != 201 {
 
83
                return nil, fmt.Errorf("SSO Error: %s\n", response.Status)
 
84
        }
 
85
        body, err := ioutil.ReadAll(response.Body)
 
86
        if err != nil {
 
87
                return nil, err
 
88
        }
 
89
        ssodata := SSOData{}
 
90
        err = json.Unmarshal(body, &ssodata)
 
91
        if err != nil {
 
92
                return nil, err
 
93
        }
 
94
        ssodata.Realm = "API"
 
95
        return &ssodata, nil
 
96
}
 
97
 
 
98
// Returns all the Ubuntu SSO information related to this account.
 
99
func (server UbuntuSSOServer) GetAccounts(ssodata *SSOData) (string, error) {
 
100
        rp := RequestParameters{
 
101
                BaseURL:         server.AccountsURL() + ssodata.ConsumerKey,
 
102
                HTTPMethod:      "GET",
 
103
                SignatureMethod: HMACSHA1{}}
 
104
 
 
105
        request, err := http.NewRequest(rp.HTTPMethod, rp.BaseURL, nil)
 
106
        if err != nil {
 
107
                return "", err
 
108
        }
 
109
        err = SignRequest(ssodata, &rp, request)
 
110
        if err != nil {
 
111
                return "", err
 
112
        }
 
113
        client := &http.Client{}
 
114
        response, err := client.Do(request)
 
115
        if err != nil {
 
116
                return "", err
 
117
        }
 
118
 
 
119
        body, err := ioutil.ReadAll(response.Body)
 
120
        if err != nil {
 
121
                return "", err
 
122
        }
 
123
        if response.StatusCode == 200 {
 
124
                return string(body), nil
 
125
        } else {
 
126
                var jsonMap map[string]interface{}
 
127
                err = json.Unmarshal(body, &jsonMap)
 
128
                // In theory, this should never happen.
 
129
                if err != nil {
 
130
                        return "", fmt.Errorf("NO_JSON_RESPONSE")
 
131
                }
 
132
                code, ok := jsonMap["code"]
 
133
                if !ok {
 
134
                        return "", fmt.Errorf("NO_CODE")
 
135
                }
 
136
                return "", fmt.Errorf("%v", code)
 
137
        }
 
138
}
 
139
 
 
140
// Given oauth credentials and a request, return it signed.
 
141
func SignRequest(
 
142
        ssodata *SSOData, rp *RequestParameters, request *http.Request) error {
 
143
        return ssodata.SignRequest(rp, request)
 
144
}
 
145
 
 
146
// Given oauth credentials return a valid http authorization header.
 
147
func GetAuthorizationHeader(
 
148
        ssodata *SSOData, rp *RequestParameters) (string, error) {
 
149
        header, err := ssodata.GetAuthorizationHeader(rp)
 
150
        return header, err
 
151
}
 
152
 
 
153
// Returns all the Ubuntu SSO information related to this token.
 
154
func (server UbuntuSSOServer) GetTokenDetails(ssodata *SSOData) (string, error) {
 
155
        rp := RequestParameters{
 
156
                BaseURL:         server.TokenDetailsURL() + ssodata.TokenKey,
 
157
                HTTPMethod:      "GET",
 
158
                SignatureMethod: HMACSHA1{}}
 
159
 
 
160
        request, err := http.NewRequest(rp.HTTPMethod, rp.BaseURL, nil)
 
161
        if err != nil {
 
162
                return "", err
 
163
        }
 
164
        err = SignRequest(ssodata, &rp, request)
 
165
        if err != nil {
 
166
                return "", err
 
167
        }
 
168
        client := &http.Client{}
 
169
        response, err := client.Do(request)
 
170
        if err != nil {
 
171
                return "", err
 
172
        }
 
173
        body, err := ioutil.ReadAll(response.Body)
 
174
        if err != nil {
 
175
                return "", err
 
176
        }
 
177
        if response.StatusCode == 200 {
 
178
                return string(body), nil
 
179
        } else {
 
180
                var jsonMap map[string]interface{}
 
181
                err = json.Unmarshal(body, &jsonMap)
 
182
                // due to bug #1285176, it is possible to get non json code in the response.
 
183
                if err != nil {
 
184
                        return "", fmt.Errorf("INVALID_CREDENTIALS")
 
185
                }
 
186
                code, ok := jsonMap["code"]
 
187
                if !ok {
 
188
                        return "", fmt.Errorf("NO_CODE")
 
189
                }
 
190
                return "", fmt.Errorf("%v", code)
 
191
        }
 
192
}
 
193
 
 
194
// Verify the validity of the token, abusing the API to get the token details.
 
195
func (server UbuntuSSOServer) IsTokenValid(ssodata *SSOData) (bool, error) {
 
196
        details, err := server.GetTokenDetails(ssodata)
 
197
        if details != "" && err == nil {
 
198
                return true, nil
 
199
        } else {
 
200
                return false, err
 
201
        }
 
202
}