-
Notifications
You must be signed in to change notification settings - Fork 170
/
cloudshell_credential.go
95 lines (78 loc) · 2.63 KB
/
cloudshell_credential.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
package auth
import (
"context"
"encoding/json"
"errors"
"fmt"
"io"
"net/http"
"net/url"
"strings"
"time"
"github.com/Azure/azure-sdk-for-go/sdk/azcore"
"github.com/Azure/azure-sdk-for-go/sdk/azcore/policy"
"github.com/azure/azure-dev/cli/azd/pkg/httputil"
)
// Use URL from https://learn.microsoft.com/azure/cloud-shell/msi-authorization
const cLocalTokenUrl = "http://localhost:50342/oauth2/token" //#nosec G101 -- This is a false positive
const cDefaultSuffix = "/.default"
type TokenFromCloudShell struct {
AccessToken string `json:"access_token"`
RefreshToken string `json:"refresh_token"`
ExpiresIn json.Number `json:"expires_in" type:"integer"`
ExpiresOn json.Number `json:"expires_on" type:"integer"`
NotBefore json.Number `json:"not_before" type:"integer"`
Resource string `json:"resource"`
TokenType string `json:"token_type"`
}
type CloudShellCredential struct {
httpClient httputil.HttpClient
}
func NewCloudShellCredential(httpClient httputil.HttpClient) *CloudShellCredential {
cloudShellCredential := CloudShellCredential{httpClient: httpClient}
return &cloudShellCredential
}
func (t CloudShellCredential) GetToken(ctx context.Context, options policy.TokenRequestOptions) (azcore.AccessToken, error) {
// Taken from azure_cli_credential.go
if len(options.Scopes) != 1 {
return azcore.AccessToken{}, errors.New("CloudShellCredential: GetToken() requires exactly one scope")
}
// API expects an AAD v1 resource, not a v2 scope
scope := strings.TrimSuffix(options.Scopes[0], cDefaultSuffix)
postData := url.Values{}
postData.Set("resource", scope)
req, err := http.NewRequestWithContext(
ctx, "POST", cLocalTokenUrl, strings.NewReader(postData.Encode()))
if err != nil {
return azcore.AccessToken{}, err
}
req.Header.Add("Metadata", "true")
req.Header.Add("Content-Type", "application/x-www-form-urlencoded")
resp, err := t.httpClient.Do(req)
if err != nil {
return azcore.AccessToken{}, err
}
defer resp.Body.Close()
responseBytes, err := io.ReadAll(resp.Body)
if err != nil {
return azcore.AccessToken{}, err
}
if resp.StatusCode != 200 {
return azcore.AccessToken{}, fmt.Errorf(
"invalid CloudShell token API response code: %d, content: %s",
resp.StatusCode,
responseBytes)
}
var tokenObject TokenFromCloudShell
if err := json.Unmarshal(responseBytes, &tokenObject); err != nil {
return azcore.AccessToken{}, err
}
expiresOnSeconds, err := tokenObject.ExpiresOn.Int64()
if err != nil {
return azcore.AccessToken{}, err
}
return azcore.AccessToken{
Token: tokenObject.AccessToken,
ExpiresOn: time.Unix(expiresOnSeconds, 0),
}, nil
}