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

« back to all changes in this revision

Viewing changes to src/github.com/juju/juju/provider/azure/internal/azureauth/interactive_test.go

  • Committer: Nicholas Skaggs
  • Date: 2016-09-30 14:39:30 UTC
  • mfrom: (1.8.1)
  • Revision ID: nicholas.skaggs@canonical.com-20160930143930-vwwhrefh6ftckccy
import upstream

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
// Copyright 2016 Canonical Ltd.
 
2
// Licensed under the AGPLv3, see LICENCE file for details.
 
3
 
 
4
package azureauth_test
 
5
 
 
6
import (
 
7
        "bytes"
 
8
        "encoding/json"
 
9
        "fmt"
 
10
        "io/ioutil"
 
11
        "net/http"
 
12
        "time"
 
13
 
 
14
        "github.com/Azure/azure-sdk-for-go/arm/authorization"
 
15
        "github.com/Azure/go-autorest/autorest"
 
16
        "github.com/Azure/go-autorest/autorest/azure"
 
17
        "github.com/Azure/go-autorest/autorest/mocks"
 
18
        "github.com/Azure/go-autorest/autorest/to"
 
19
        "github.com/juju/testing"
 
20
        jc "github.com/juju/testing/checkers"
 
21
        "github.com/juju/utils"
 
22
        gc "gopkg.in/check.v1"
 
23
 
 
24
        "github.com/juju/juju/provider/azure/internal/ad"
 
25
        "github.com/juju/juju/provider/azure/internal/azureauth"
 
26
        "github.com/juju/juju/provider/azure/internal/azuretesting"
 
27
)
 
28
 
 
29
func clockStartTime() time.Time {
 
30
        t, _ := time.Parse("2006-Jan-02 3:04am", "2016-Sep-19 9:47am")
 
31
        return t
 
32
}
 
33
 
 
34
type InteractiveSuite struct {
 
35
        testing.IsolationSuite
 
36
        clock   *testing.Clock
 
37
        newUUID func() (utils.UUID, error)
 
38
}
 
39
 
 
40
var _ = gc.Suite(&InteractiveSuite{})
 
41
 
 
42
func deviceCodeSender() autorest.Sender {
 
43
        return azuretesting.NewSenderWithValue(azure.DeviceCode{
 
44
                DeviceCode: to.StringPtr("device-code"),
 
45
                Interval:   to.Int64Ptr(1), // 1 second between polls
 
46
                Message:    to.StringPtr("open your browser, etc."),
 
47
        })
 
48
}
 
49
 
 
50
func tokenSender() autorest.Sender {
 
51
        return azuretesting.NewSenderWithValue(azure.Token{
 
52
                RefreshToken: "refresh-token",
 
53
                ExpiresOn:    fmt.Sprint(time.Now().Add(time.Hour).Unix()),
 
54
        })
 
55
}
 
56
 
 
57
func passwordCredentialsListSender() autorest.Sender {
 
58
        return azuretesting.NewSenderWithValue(ad.PasswordCredentialsListResult{
 
59
                Value: []ad.PasswordCredential{{
 
60
                        KeyId: "password-credential-key-id",
 
61
                }},
 
62
        })
 
63
}
 
64
 
 
65
func updatePasswordCredentialsSender() autorest.Sender {
 
66
        sender := mocks.NewSender()
 
67
        sender.AppendResponse(mocks.NewResponseWithStatus("", http.StatusNoContent))
 
68
        return sender
 
69
}
 
70
 
 
71
func currentUserSender() autorest.Sender {
 
72
        return azuretesting.NewSenderWithValue(ad.AADObject{
 
73
                DisplayName: "Foo Bar",
 
74
        })
 
75
}
 
76
 
 
77
func createServicePrincipalSender() autorest.Sender {
 
78
        return azuretesting.NewSenderWithValue(ad.ServicePrincipal{
 
79
                ApplicationID: "cbb548f1-5039-4836-af0b-727e8571f6a9",
 
80
                ObjectID:      "sp-object-id",
 
81
        })
 
82
}
 
83
 
 
84
func createServicePrincipalAlreadyExistsSender() autorest.Sender {
 
85
        sender := mocks.NewSender()
 
86
        body := mocks.NewBody(`{"odata.error":{"code":"Request_MultipleObjectsWithSameKeyValue"}}`)
 
87
        sender.AppendResponse(mocks.NewResponseWithBodyAndStatus(body, http.StatusConflict, ""))
 
88
        return sender
 
89
}
 
90
 
 
91
func servicePrincipalListSender() autorest.Sender {
 
92
        return azuretesting.NewSenderWithValue(ad.ServicePrincipalListResult{
 
93
                Value: []ad.ServicePrincipal{{
 
94
                        ApplicationID: "cbb548f1-5039-4836-af0b-727e8571f6a9",
 
95
                        ObjectID:      "sp-object-id",
 
96
                }},
 
97
        })
 
98
}
 
99
 
 
100
func roleDefinitionListSender() autorest.Sender {
 
101
        roleDefinitions := []authorization.RoleDefinition{{
 
102
                ID:   to.StringPtr("owner-role-id"),
 
103
                Name: to.StringPtr("Owner"),
 
104
        }}
 
105
        return azuretesting.NewSenderWithValue(authorization.RoleDefinitionListResult{
 
106
                Value: &roleDefinitions,
 
107
        })
 
108
}
 
109
 
 
110
func roleAssignmentSender() autorest.Sender {
 
111
        return azuretesting.NewSenderWithValue(authorization.RoleAssignment{})
 
112
}
 
113
 
 
114
func roleAssignmentAlreadyExistsSender() autorest.Sender {
 
115
        sender := mocks.NewSender()
 
116
        body := mocks.NewBody(`{"error":{"code":"RoleAssignmentExists"}}`)
 
117
        sender.AppendResponse(mocks.NewResponseWithBodyAndStatus(body, http.StatusConflict, ""))
 
118
        return sender
 
119
}
 
120
 
 
121
func (s *InteractiveSuite) SetUpTest(c *gc.C) {
 
122
        s.IsolationSuite.SetUpTest(c)
 
123
        uuids := []string{
 
124
                "33333333-3333-3333-3333-333333333333", // password
 
125
                "44444444-4444-4444-4444-444444444444", // password key ID
 
126
                "55555555-5555-5555-5555-555555555555", // role assignment ID
 
127
        }
 
128
        s.newUUID = func() (utils.UUID, error) {
 
129
                uuid, err := utils.UUIDFromString(uuids[0])
 
130
                if err != nil {
 
131
                        return utils.UUID{}, err
 
132
                }
 
133
                uuids = uuids[1:]
 
134
                return uuid, nil
 
135
        }
 
136
        s.clock = testing.NewClock(clockStartTime())
 
137
}
 
138
 
 
139
func (s *InteractiveSuite) TestInteractive(c *gc.C) {
 
140
 
 
141
        var requests []*http.Request
 
142
        senders := azuretesting.Senders{
 
143
                oauthConfigSender(),
 
144
                deviceCodeSender(),
 
145
                tokenSender(), // CheckForUserCompletion returns a token.
 
146
 
 
147
                // Token.Refresh returns a token. We do this
 
148
                // twice: once for ARM, and once for AAD.
 
149
                tokenSender(),
 
150
                tokenSender(),
 
151
 
 
152
                currentUserSender(),
 
153
                createServicePrincipalSender(),
 
154
                roleDefinitionListSender(),
 
155
                roleAssignmentSender(),
 
156
        }
 
157
 
 
158
        var stderr bytes.Buffer
 
159
        subscriptionId := "22222222-2222-2222-2222-222222222222"
 
160
        appId, password, err := azureauth.InteractiveCreateServicePrincipal(
 
161
                &stderr,
 
162
                &senders,
 
163
                azuretesting.RequestRecorder(&requests),
 
164
                "https://arm.invalid",
 
165
                "https://graph.invalid",
 
166
                subscriptionId,
 
167
                s.clock,
 
168
                s.newUUID,
 
169
        )
 
170
        c.Assert(err, jc.ErrorIsNil)
 
171
        c.Assert(appId, gc.Equals, "cbb548f1-5039-4836-af0b-727e8571f6a9")
 
172
        c.Assert(password, gc.Equals, "33333333-3333-3333-3333-333333333333")
 
173
        c.Assert(stderr.String(), gc.Equals, `
 
174
Initiating interactive authentication.
 
175
 
 
176
open your browser, etc.
 
177
 
 
178
Authenticated as "Foo Bar".
 
179
Creating/updating service principal.
 
180
Assigning Owner role to service principal.
 
181
`[1:])
 
182
 
 
183
        // Token refreshes don't go through the inspectors.
 
184
        c.Assert(requests, gc.HasLen, 7)
 
185
        c.Check(requests[0].URL.Path, gc.Equals, "/subscriptions/22222222-2222-2222-2222-222222222222")
 
186
        c.Check(requests[1].URL.Path, gc.Equals, "/11111111-1111-1111-1111-111111111111/oauth2/devicecode")
 
187
        c.Check(requests[2].URL.Path, gc.Equals, "/11111111-1111-1111-1111-111111111111/oauth2/token")
 
188
        c.Check(requests[3].URL.Path, gc.Equals, "/11111111-1111-1111-1111-111111111111/me")
 
189
        c.Check(requests[4].URL.Path, gc.Equals, "/11111111-1111-1111-1111-111111111111/servicePrincipals")
 
190
        c.Check(requests[5].URL.Path, gc.Equals, "/subscriptions/22222222-2222-2222-2222-222222222222/providers/Microsoft.Authorization/roleDefinitions")
 
191
        c.Check(requests[6].URL.Path, gc.Equals, "/subscriptions/22222222-2222-2222-2222-222222222222/providers/Microsoft.Authorization/roleAssignments/55555555-5555-5555-5555-555555555555")
 
192
 
 
193
        // The service principal creation includes the password. Check that the
 
194
        // password returned from the function is the same as the one set in the
 
195
        // request.
 
196
        var params ad.ServicePrincipalCreateParameters
 
197
        err = json.NewDecoder(requests[4].Body).Decode(&params)
 
198
        c.Assert(err, jc.ErrorIsNil)
 
199
        c.Assert(params.PasswordCredentials, gc.HasLen, 1)
 
200
        assertPasswordCredential(c, params.PasswordCredentials[0])
 
201
}
 
202
 
 
203
func assertPasswordCredential(c *gc.C, cred ad.PasswordCredential) {
 
204
        startDate := cred.StartDate
 
205
        endDate := cred.EndDate
 
206
        c.Assert(startDate, gc.Equals, clockStartTime())
 
207
        c.Assert(endDate.Sub(startDate), gc.Equals, 365*24*time.Hour)
 
208
 
 
209
        cred.StartDate = time.Time{}
 
210
        cred.EndDate = time.Time{}
 
211
        c.Assert(cred, jc.DeepEquals, ad.PasswordCredential{
 
212
                CustomKeyIdentifier: []byte("juju-20160919"),
 
213
                KeyId:               "44444444-4444-4444-4444-444444444444",
 
214
                Value:               "33333333-3333-3333-3333-333333333333",
 
215
        })
 
216
}
 
217
 
 
218
func (s *InteractiveSuite) TestInteractiveRoleAssignmentAlreadyExists(c *gc.C) {
 
219
        var requests []*http.Request
 
220
        senders := azuretesting.Senders{
 
221
                oauthConfigSender(),
 
222
                deviceCodeSender(),
 
223
                tokenSender(),
 
224
                tokenSender(),
 
225
                tokenSender(),
 
226
                currentUserSender(),
 
227
                createServicePrincipalSender(),
 
228
                roleDefinitionListSender(),
 
229
                roleAssignmentAlreadyExistsSender(),
 
230
        }
 
231
        _, _, err := azureauth.InteractiveCreateServicePrincipal(
 
232
                ioutil.Discard,
 
233
                &senders,
 
234
                azuretesting.RequestRecorder(&requests),
 
235
                "https://arm.invalid",
 
236
                "https://graph.invalid",
 
237
                "22222222-2222-2222-2222-222222222222",
 
238
                s.clock,
 
239
                s.newUUID,
 
240
        )
 
241
        c.Assert(err, jc.ErrorIsNil)
 
242
}
 
243
 
 
244
func (s *InteractiveSuite) TestInteractiveServicePrincipalAlreadyExists(c *gc.C) {
 
245
        var requests []*http.Request
 
246
        senders := azuretesting.Senders{
 
247
                oauthConfigSender(),
 
248
                deviceCodeSender(),
 
249
                tokenSender(),
 
250
                tokenSender(),
 
251
                tokenSender(),
 
252
                currentUserSender(),
 
253
                createServicePrincipalAlreadyExistsSender(),
 
254
                servicePrincipalListSender(),
 
255
                passwordCredentialsListSender(),
 
256
                updatePasswordCredentialsSender(),
 
257
                roleDefinitionListSender(),
 
258
                roleAssignmentAlreadyExistsSender(),
 
259
        }
 
260
        _, password, err := azureauth.InteractiveCreateServicePrincipal(
 
261
                ioutil.Discard,
 
262
                &senders,
 
263
                azuretesting.RequestRecorder(&requests),
 
264
                "https://arm.invalid",
 
265
                "https://graph.invalid",
 
266
                "22222222-2222-2222-2222-222222222222",
 
267
                s.clock,
 
268
                s.newUUID,
 
269
        )
 
270
        c.Assert(err, jc.ErrorIsNil)
 
271
        c.Assert(password, gc.Equals, "33333333-3333-3333-3333-333333333333")
 
272
 
 
273
        c.Assert(requests, gc.HasLen, 10)
 
274
        c.Check(requests[0].URL.Path, gc.Equals, "/subscriptions/22222222-2222-2222-2222-222222222222")
 
275
        c.Check(requests[1].URL.Path, gc.Equals, "/11111111-1111-1111-1111-111111111111/oauth2/devicecode")
 
276
        c.Check(requests[2].URL.Path, gc.Equals, "/11111111-1111-1111-1111-111111111111/oauth2/token")
 
277
        c.Check(requests[3].URL.Path, gc.Equals, "/11111111-1111-1111-1111-111111111111/me")
 
278
        c.Check(requests[4].URL.Path, gc.Equals, "/11111111-1111-1111-1111-111111111111/servicePrincipals")                                  // create
 
279
        c.Check(requests[5].URL.Path, gc.Equals, "/11111111-1111-1111-1111-111111111111/servicePrincipals")                                  // list
 
280
        c.Check(requests[6].URL.Path, gc.Equals, "/11111111-1111-1111-1111-111111111111/servicePrincipals/sp-object-id/passwordCredentials") // list
 
281
        c.Check(requests[7].URL.Path, gc.Equals, "/11111111-1111-1111-1111-111111111111/servicePrincipals/sp-object-id/passwordCredentials") // update
 
282
        c.Check(requests[8].URL.Path, gc.Equals, "/subscriptions/22222222-2222-2222-2222-222222222222/providers/Microsoft.Authorization/roleDefinitions")
 
283
        c.Check(requests[9].URL.Path, gc.Equals, "/subscriptions/22222222-2222-2222-2222-222222222222/providers/Microsoft.Authorization/roleAssignments/55555555-5555-5555-5555-555555555555")
 
284
 
 
285
        // Make sure that we don't wipe existing password credentials, and that
 
286
        // the new password credential matches the one returned from the
 
287
        // function.
 
288
        var params ad.PasswordCredentialsUpdateParameters
 
289
        err = json.NewDecoder(requests[7].Body).Decode(&params)
 
290
        c.Assert(err, jc.ErrorIsNil)
 
291
        c.Assert(params.Value, gc.HasLen, 2)
 
292
        c.Assert(params.Value[0], jc.DeepEquals, ad.PasswordCredential{
 
293
                KeyId:     "password-credential-key-id",
 
294
                StartDate: time.Time{}.UTC(),
 
295
                EndDate:   time.Time{}.UTC(),
 
296
        })
 
297
        assertPasswordCredential(c, params.Value[1])
 
298
}