Skip to content

Commit 3bd9e4c

Browse files
committed
chore: Add Azure DevOps adapter
Signed-off-by: Jorge Turrado <jorge.turrado@mail.schwarz>
1 parent 47e0f29 commit 3bd9e4c

File tree

1 file changed

+73
-0
lines changed

1 file changed

+73
-0
lines changed

core/oidcadapters/azuredevops.go

Lines changed: 73 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,73 @@
1+
package oidcadapters
2+
3+
import (
4+
"context"
5+
"encoding/json"
6+
"fmt"
7+
"io"
8+
"net/http"
9+
"net/url"
10+
)
11+
12+
const (
13+
adoPipelineOIDCAPIVersion = "7.1"
14+
adoAudience = "api://AzureADTokenExchange"
15+
)
16+
17+
func RequestAzureDevOpsOIDCToken(oidcRequestUrl, oidcRequestToken, serviceConnectionID string) OIDCTokenFunc {
18+
return func(ctx context.Context) (string, error) {
19+
req, err := http.NewRequestWithContext(ctx, http.MethodGet, oidcRequestUrl, http.NoBody)
20+
if err != nil {
21+
return "", fmt.Errorf("azureDevOpsAssertion: failed to build request: %w", err)
22+
}
23+
24+
query, err := url.ParseQuery(req.URL.RawQuery)
25+
if err != nil {
26+
return "", fmt.Errorf("azureDevOpsAssertion: cannot parse URL query")
27+
}
28+
29+
if query.Get("api-version") == "" {
30+
query.Add("api-version", adoPipelineOIDCAPIVersion)
31+
}
32+
33+
if query.Get("serviceConnectionId") == "" && serviceConnectionID != "" {
34+
query.Add("serviceConnectionId", serviceConnectionID)
35+
}
36+
37+
if query.Get("audience") == "" {
38+
query.Set("audience", adoAudience) // Azure DevOps requires this specific audience for OIDC tokens
39+
}
40+
41+
req.URL.RawQuery = query.Encode()
42+
43+
req.Header.Set("Accept", "application/json")
44+
req.Header.Set("Authorization", fmt.Sprintf("Bearer %s", oidcRequestToken))
45+
req.Header.Set("Content-Type", "application/x-www-form-urlencoded")
46+
47+
resp, err := http.DefaultClient.Do(req)
48+
if err != nil {
49+
return "", fmt.Errorf("azureDevOpsAssertion: cannot request token: %w", err)
50+
}
51+
52+
defer func() {
53+
_ = resp.Body.Close()
54+
}()
55+
body, err := io.ReadAll(io.LimitReader(resp.Body, 1<<20))
56+
if err != nil {
57+
return "", fmt.Errorf("azureDevOpsAssertion: cannot parse response: %w", err)
58+
}
59+
60+
if c := resp.StatusCode; c < 200 || c > 299 {
61+
return "", fmt.Errorf("azureDevOpsAssertion: received HTTP status %d with response: %s", resp.StatusCode, body)
62+
}
63+
64+
var tokenRes struct {
65+
Value string `json:"value"`
66+
}
67+
if err := json.Unmarshal(body, &tokenRes); err != nil {
68+
return "", fmt.Errorf("azureDevOpsAssertion: cannot unmarshal response: %w", err)
69+
}
70+
71+
return tokenRes.Value, nil
72+
}
73+
}

0 commit comments

Comments
 (0)