github.com/axw/juju@v0.0.0-20161005053422-4bd6544d08d4/provider/azure/internal/azureauth/interactive_test.go (about) 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(¶ms) 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(¶ms) 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 }