2020-06-18 16:13:24 +02:00
|
|
|
/*
|
2020-09-22 12:13:00 +02:00
|
|
|
Copyright 2020 Docker Compose CLI authors
|
2020-06-18 16:13:24 +02:00
|
|
|
|
|
|
|
Licensed under the Apache License, Version 2.0 (the "License");
|
|
|
|
you may not use this file except in compliance with the License.
|
|
|
|
You may obtain a copy of the License at
|
|
|
|
|
|
|
|
http://www.apache.org/licenses/LICENSE-2.0
|
|
|
|
|
|
|
|
Unless required by applicable law or agreed to in writing, software
|
|
|
|
distributed under the License is distributed on an "AS IS" BASIS,
|
|
|
|
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
|
|
See the License for the specific language governing permissions and
|
|
|
|
limitations under the License.
|
|
|
|
*/
|
|
|
|
|
2020-05-15 10:40:25 +02:00
|
|
|
package login
|
|
|
|
|
|
|
|
import (
|
2020-09-03 18:21:30 +02:00
|
|
|
"context"
|
2020-05-15 10:40:25 +02:00
|
|
|
"encoding/json"
|
|
|
|
"fmt"
|
|
|
|
"io/ioutil"
|
|
|
|
"math/rand"
|
|
|
|
"net/http"
|
|
|
|
"net/url"
|
2020-07-07 09:29:45 +02:00
|
|
|
"os/exec"
|
|
|
|
"runtime"
|
2020-05-15 10:40:25 +02:00
|
|
|
"strings"
|
|
|
|
|
2020-09-02 14:43:16 +02:00
|
|
|
"github.com/Azure/go-autorest/autorest/adal"
|
|
|
|
"github.com/Azure/go-autorest/autorest/azure/auth"
|
|
|
|
|
2020-05-15 10:40:25 +02:00
|
|
|
"github.com/pkg/errors"
|
|
|
|
)
|
|
|
|
|
2020-08-07 12:55:12 +02:00
|
|
|
var (
|
|
|
|
letterRunes = []rune("abcdefghijklmnopqrstuvwxyz123456789")
|
|
|
|
)
|
|
|
|
|
2020-05-15 10:40:25 +02:00
|
|
|
type apiHelper interface {
|
2021-01-31 12:15:00 -07:00
|
|
|
queryToken(ce CloudEnvironment, data url.Values, tenantID string) (azureToken, error)
|
|
|
|
openAzureLoginPage(redirectURL string, ce CloudEnvironment) error
|
2020-09-03 18:21:30 +02:00
|
|
|
queryAPIWithHeader(ctx context.Context, authorizationURL string, authorizationHeader string) ([]byte, int, error)
|
2021-01-31 12:15:00 -07:00
|
|
|
getDeviceCodeFlowToken(ce CloudEnvironment) (adal.Token, error)
|
2020-05-15 10:40:25 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
type azureAPIHelper struct{}
|
|
|
|
|
2021-01-31 12:15:00 -07:00
|
|
|
func (helper azureAPIHelper) getDeviceCodeFlowToken(ce CloudEnvironment) (adal.Token, error) {
|
2020-09-02 14:43:16 +02:00
|
|
|
deviceconfig := auth.NewDeviceFlowConfig(clientID, "common")
|
2021-01-31 12:15:00 -07:00
|
|
|
deviceconfig.Resource = ce.ResourceManagerURL
|
2020-09-02 14:43:16 +02:00
|
|
|
spToken, err := deviceconfig.ServicePrincipalToken()
|
|
|
|
if err != nil {
|
|
|
|
return adal.Token{}, err
|
|
|
|
}
|
|
|
|
return spToken.Token(), err
|
|
|
|
}
|
|
|
|
|
2021-01-31 12:15:00 -07:00
|
|
|
func (helper azureAPIHelper) openAzureLoginPage(redirectURL string, ce CloudEnvironment) error {
|
2020-05-15 10:40:25 +02:00
|
|
|
state := randomString("", 10)
|
2021-01-31 12:15:00 -07:00
|
|
|
authURL := fmt.Sprintf(ce.GetAuthorizeRequestFormat(), clientID, redirectURL, state, ce.GetTokenScope())
|
2020-07-07 09:29:45 +02:00
|
|
|
return openbrowser(authURL)
|
2020-05-15 10:40:25 +02:00
|
|
|
}
|
|
|
|
|
2020-09-03 18:21:30 +02:00
|
|
|
func (helper azureAPIHelper) queryAPIWithHeader(ctx context.Context, authorizationURL string, authorizationHeader string) ([]byte, int, error) {
|
2020-05-15 10:40:25 +02:00
|
|
|
req, err := http.NewRequest(http.MethodGet, authorizationURL, nil)
|
|
|
|
if err != nil {
|
|
|
|
return nil, 0, err
|
|
|
|
}
|
2020-09-03 18:21:30 +02:00
|
|
|
req = req.WithContext(ctx)
|
2020-05-15 10:40:25 +02:00
|
|
|
req.Header.Add("Authorization", authorizationHeader)
|
|
|
|
res, err := http.DefaultClient.Do(req)
|
|
|
|
if err != nil {
|
|
|
|
return nil, 0, err
|
|
|
|
}
|
|
|
|
bits, err := ioutil.ReadAll(res.Body)
|
|
|
|
if err != nil {
|
|
|
|
return nil, 0, err
|
|
|
|
}
|
|
|
|
return bits, res.StatusCode, nil
|
|
|
|
}
|
|
|
|
|
2021-01-31 12:15:00 -07:00
|
|
|
func (helper azureAPIHelper) queryToken(ce CloudEnvironment, data url.Values, tenantID string) (azureToken, error) {
|
|
|
|
res, err := http.Post(fmt.Sprintf(ce.GetTokenRequestFormat(), tenantID), "application/x-www-form-urlencoded", strings.NewReader(data.Encode()))
|
2020-05-15 10:40:25 +02:00
|
|
|
if err != nil {
|
|
|
|
return azureToken{}, err
|
|
|
|
}
|
|
|
|
if res.StatusCode != 200 {
|
|
|
|
return azureToken{}, errors.Errorf("error while renewing access token, status : %s", res.Status)
|
|
|
|
}
|
|
|
|
bits, err := ioutil.ReadAll(res.Body)
|
|
|
|
if err != nil {
|
|
|
|
return azureToken{}, err
|
|
|
|
}
|
|
|
|
token := azureToken{}
|
|
|
|
if err := json.Unmarshal(bits, &token); err != nil {
|
|
|
|
return azureToken{}, err
|
|
|
|
}
|
|
|
|
return token, nil
|
|
|
|
}
|
|
|
|
|
2020-08-07 12:55:12 +02:00
|
|
|
func openbrowser(address string) error {
|
2020-07-07 09:29:45 +02:00
|
|
|
switch runtime.GOOS {
|
|
|
|
case "linux":
|
2020-08-07 12:55:12 +02:00
|
|
|
if isWsl() {
|
2020-09-02 17:31:04 +02:00
|
|
|
return exec.Command("wslview", address).Run()
|
2020-08-07 12:55:12 +02:00
|
|
|
}
|
2020-09-02 17:31:04 +02:00
|
|
|
return exec.Command("xdg-open", address).Run()
|
2020-07-07 09:29:45 +02:00
|
|
|
case "windows":
|
2020-09-02 17:31:04 +02:00
|
|
|
return exec.Command("rundll32", "url.dll,FileProtocolHandler", address).Run()
|
2020-07-07 09:29:45 +02:00
|
|
|
case "darwin":
|
2020-09-02 17:31:04 +02:00
|
|
|
return exec.Command("open", address).Run()
|
2020-07-07 09:29:45 +02:00
|
|
|
default:
|
|
|
|
return fmt.Errorf("unsupported platform")
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-08-07 12:55:12 +02:00
|
|
|
func isWsl() bool {
|
|
|
|
b, err := ioutil.ReadFile("/proc/version")
|
|
|
|
if err != nil {
|
|
|
|
return false
|
|
|
|
}
|
|
|
|
|
2021-05-15 21:59:52 +01:00
|
|
|
return strings.Contains(strings.ToLower(string(b)), "microsoft")
|
2020-08-07 12:55:12 +02:00
|
|
|
}
|
2020-05-15 10:40:25 +02:00
|
|
|
|
|
|
|
func randomString(prefix string, length int) string {
|
|
|
|
b := make([]rune, length)
|
|
|
|
for i := range b {
|
|
|
|
b[i] = letterRunes[rand.Intn(len(letterRunes))]
|
|
|
|
}
|
|
|
|
return prefix + string(b)
|
|
|
|
}
|