Skip to content

Commit

Permalink
Merge pull request #309 from AzureAD/release-0.5.0
Browse files Browse the repository at this point in the history
MSAL Python 0.5.0
  • Loading branch information
abhidnya13 authored Apr 28, 2022
2 parents 97e99ae + fc35404 commit f7df979
Show file tree
Hide file tree
Showing 9 changed files with 145 additions and 18 deletions.
7 changes: 5 additions & 2 deletions .github/workflows/golangci-lint.yml
Original file line number Diff line number Diff line change
Expand Up @@ -16,9 +16,12 @@ jobs:
name: lint
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v2
- uses: actions/setup-go@v3
with:
go-version: 1.17
- uses: actions/checkout@v3
- name: golangci-lint
uses: golangci/golangci-lint-action@v2
uses: golangci/golangci-lint-action@v3
with:
# Required: the version of golangci-lint is required and must be specified without patch version: we always use the latest patch version.
version: v1.32
Expand Down
27 changes: 26 additions & 1 deletion apps/confidential/confidential.go
Original file line number Diff line number Diff line change
Expand Up @@ -169,6 +169,11 @@ func NewCredFromCert(cert *x509.Certificate, key crypto.PrivateKey) Credential {
return Credential{cert: cert, key: key}
}

// AutoDetectRegion instructs MSAL Go to auto detect region for Azure regional token service.
func AutoDetectRegion() string {
return "TryAutoDetect"
}

// Client is a representation of authentication client for confidential applications as defined in the
// package doc. A new Client should be created PER SERVICE USER.
// For more information, visit https://docs.microsoft.com/azure/active-directory/develop/msal-client-applications
Expand Down Expand Up @@ -200,6 +205,9 @@ type Options struct {

// SendX5C specifies if x5c claim(public key of the certificate) should be sent to STS.
SendX5C bool

// Instructs MSAL Go to use an Azure regional token service with sepcified AzureRegion.
AzureRegion string
}

func (o Options) validate() error {
Expand Down Expand Up @@ -245,6 +253,23 @@ func WithX5C() Option {
}
}

// WithAzureRegion sets the region(preferred) or Confidential.AutoDetectRegion() for auto detecting region.
// Region names as per https://azure.microsoft.com/en-ca/global-infrastructure/geographies/.
// See https://aka.ms/region-map for more details on region names.
// The region value should be short region name for the region where the service is deployed.
// For example "centralus" is short name for region Central US.
// Not all auth flows can use the regional token service.
// Service To Service (client credential flow) tokens can be obtained from the regional service.
// Requires configuration at the tenant level.
// Auto-detection works on a limited number of Azure artifacts (VMs, Azure functions).
// If auto-detection fails, the non-regional endpoint will be used.
// If an invalid region name is provided, the non-regional endpoint MIGHT be used or the token request MIGHT fail.
func WithAzureRegion(val string) Option {
return func(o *Options) {
o.AzureRegion = val
}
}

// New is the constructor for Client. userID is the unique identifier of the user this client
// will store credentials for (a Client is per user). clientID is the Azure clientID and cred is
// the type of credential to use.
Expand All @@ -261,7 +286,7 @@ func New(clientID string, cred Credential, options ...Option) (Client, error) {
return Client{}, err
}

base, err := base.New(clientID, opts.Authority, oauth.New(opts.HTTPClient), base.WithX5C(opts.SendX5C), base.WithCacheAccessor(opts.Accessor))
base, err := base.New(clientID, opts.Authority, oauth.New(opts.HTTPClient), base.WithX5C(opts.SendX5C), base.WithCacheAccessor(opts.Accessor), base.WithRegionDetection(opts.AzureRegion))
if err != nil {
return Client{}, err
}
Expand Down
6 changes: 6 additions & 0 deletions apps/internal/base/base.go
Original file line number Diff line number Diff line change
Expand Up @@ -154,6 +154,12 @@ func WithX5C(sendX5C bool) Option {
}
}

func WithRegionDetection(region string) Option {
return func(c *Client) {
c.AuthParams.AuthorityInfo.Region = region
}
}

// New is the constructor for Base.
func New(clientID string, authorityURI string, token *oauth.Client, options ...Option) (Client, error) {
authInfo, err := authority.NewInfoFromAuthorityURI(authorityURI, true)
Expand Down
6 changes: 5 additions & 1 deletion apps/internal/oauth/ops/accesstokens/accesstokens.go
Original file line number Diff line number Diff line change
Expand Up @@ -394,7 +394,7 @@ var detectDefaultScopes = map[string]bool{

var defaultScopes = []string{"openid", "offline_access", "profile"}

func addScopeQueryParam(queryParams url.Values, authParameters authority.AuthParams) {
func AppendDefaultScopes(authParameters authority.AuthParams) []string {
scopes := make([]string, 0, len(authParameters.Scopes)+len(defaultScopes))
for _, scope := range authParameters.Scopes {
s := strings.TrimSpace(scope)
Expand All @@ -407,6 +407,10 @@ func addScopeQueryParam(queryParams url.Values, authParameters authority.AuthPar
scopes = append(scopes, scope)
}
scopes = append(scopes, defaultScopes...)
return scopes
}

func addScopeQueryParam(queryParams url.Values, authParameters authority.AuthParams) {
scopes := AppendDefaultScopes(authParameters)
queryParams.Set("scope", strings.Join(scopes, " "))
}
81 changes: 68 additions & 13 deletions apps/internal/oauth/ops/authority/authority.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,17 +9,25 @@ import (
"encoding/base64"
"errors"
"fmt"
"io/ioutil"
"net/http"
"net/url"
"os"
"strings"
"time"

"github.com/google/uuid"
)

const (
authorizationEndpoint = "https://%v/%v/oauth2/v2.0/authorize"
instanceDiscoveryEndpoint = "https://%v/common/discovery/instance"
defaultHost = "login.microsoftonline.com"
authorizationEndpoint = "https://%v/%v/oauth2/v2.0/authorize"
instanceDiscoveryEndpoint = "https://%v/common/discovery/instance"
TenantDiscoveryEndpointWithRegion = "https://%v.r.%v/%v/v2.0/.well-known/openid-configuration"
regionName = "REGION_NAME"
defaultAPIVersion = "2021-10-01"
imdsEndpoint = "http://169.254.169.254/metadata/instance/compute/location?format=text&api-version=" + defaultAPIVersion
defaultHost = "login.microsoftonline.com"
autoDetectRegion = "TryAutoDetect"
)

type jsonCaller interface {
Expand Down Expand Up @@ -167,6 +175,7 @@ type Info struct {
UserRealmURIPrefix string
ValidateAuthority bool
Tenant string
Region string
}

func firstPathSegment(u *url.URL) (string, error) {
Expand Down Expand Up @@ -314,22 +323,68 @@ func (c Client) GetTenantDiscoveryResponse(ctx context.Context, openIDConfigurat
}

func (c Client) AADInstanceDiscovery(ctx context.Context, authorityInfo Info) (InstanceDiscoveryResponse, error) {
qv := url.Values{}
qv.Set("api-version", "1.1")
qv.Set("authorization_endpoint", fmt.Sprintf(authorizationEndpoint, authorityInfo.Host, authorityInfo.Tenant))

discoveryHost := defaultHost
if TrustedHost(authorityInfo.Host) {
discoveryHost = authorityInfo.Host
region := ""
var err error
resp := InstanceDiscoveryResponse{}
if authorityInfo.Region != "" && authorityInfo.Region != autoDetectRegion {
region = authorityInfo.Region
} else if authorityInfo.Region == autoDetectRegion {
region = detectRegion(ctx)
}
if region != "" {
resp.TenantDiscoveryEndpoint = fmt.Sprintf(TenantDiscoveryEndpointWithRegion, region, authorityInfo.Host, authorityInfo.Tenant)
metadata := InstanceDiscoveryMetadata{
PreferredNetwork: fmt.Sprintf("%v.%v", region, authorityInfo.Host),
PreferredCache: authorityInfo.Host,
Aliases: []string{fmt.Sprintf("%v.%v", region, authorityInfo.Host), authorityInfo.Host},
}
resp.Metadata = []InstanceDiscoveryMetadata{metadata}
} else {
qv := url.Values{}
qv.Set("api-version", "1.1")
qv.Set("authorization_endpoint", fmt.Sprintf(authorizationEndpoint, authorityInfo.Host, authorityInfo.Tenant))

endpoint := fmt.Sprintf(instanceDiscoveryEndpoint, discoveryHost)
discoveryHost := defaultHost
if TrustedHost(authorityInfo.Host) {
discoveryHost = authorityInfo.Host
}

resp := InstanceDiscoveryResponse{}
err := c.Comm.JSONCall(ctx, endpoint, http.Header{}, qv, nil, &resp)
endpoint := fmt.Sprintf(instanceDiscoveryEndpoint, discoveryHost)
err = c.Comm.JSONCall(ctx, endpoint, http.Header{}, qv, nil, &resp)
}
return resp, err
}

func detectRegion(ctx context.Context) string {
region := os.Getenv(regionName)
if region != "" {
region = strings.ReplaceAll(region, " ", "")
return strings.ToLower(region)
}
// HTTP call to IMDS endpoint to get region
// Refer : https://identitydivision.visualstudio.com/DevEx/_git/AuthLibrariesApiReview?path=%2FPinAuthToRegion%2FAAD%20SDK%20Proposal%20to%20Pin%20Auth%20to%20region.md&_a=preview&version=GBdev
// Set a 2 second timeout for this http client which only does calls to IMDS endpoint
client := http.Client{
Timeout: time.Duration(2 * time.Second),
}
req, _ := http.NewRequest("GET", imdsEndpoint, nil)
req.Header.Set("Metadata", "true")
resp, err := client.Do(req)
// If the request times out or there is an error, it is retried once
if err != nil || resp.StatusCode != 200 {
resp, err = client.Do(req)
if err != nil || resp.StatusCode != 200 {
return ""
}
}
defer resp.Body.Close()
response, err := ioutil.ReadAll(resp.Body)
if err != nil {
return ""
}
return string(response)
}

func (a *AuthParams) CacheKey(isAppCache bool) string {
if a.AuthorizationType == ATOnBehalfOf {
return a.AssertionHash()
Expand Down
26 changes: 26 additions & 0 deletions apps/internal/oauth/ops/authority/authority_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -262,6 +262,32 @@ func TestAADInstanceDiscovery(t *testing.T) {
}
}

func TestAADInstanceDiscoveryWithRegion(t *testing.T) {
fake := &fakeJSONCaller{}
client := Client{fake}
authInfo := Info{
Host: "host",
Tenant: "tenant",
Region: "eastus",
}
resp, err := client.AADInstanceDiscovery(context.Background(), authInfo)
if err != nil {
t.Errorf("AADInstanceDiscoveryWithRegion failing with %s", err)
}
expectedTenantDiscoveryEndpoint := fmt.Sprintf(TenantDiscoveryEndpointWithRegion, "eastus", "host", "tenant")
expectedPreferredNetwork := fmt.Sprintf("%v.%v", "eastus", "host")
expectedPreferredCache := "host"
if resp.TenantDiscoveryEndpoint != expectedTenantDiscoveryEndpoint {
t.Errorf("AADInstanceDiscoveryWithRegion incorrect TenantDiscoveryEndpoint: got: %s , want: %s", resp.TenantDiscoveryEndpoint, expectedTenantDiscoveryEndpoint)
}
if resp.Metadata[0].PreferredNetwork != expectedPreferredNetwork {
t.Errorf("AADInstanceDiscoveryWithRegion incorrect Preferred Network got: %s , want: %s", resp.Metadata[0].PreferredNetwork, expectedPreferredNetwork)
}
if resp.Metadata[0].PreferredCache != expectedPreferredCache {
t.Errorf("AADInstanceDiscoveryWithRegion incorrect Preferred Cache got: %s , want: %s", resp.Metadata[0].PreferredCache, expectedPreferredCache)

}
}
func TestCreateAuthorityInfoFromAuthorityUri(t *testing.T) {
const authorityURI = "https://login.microsoftonline.com/common/"

Expand Down
7 changes: 7 additions & 0 deletions apps/internal/oauth/resolvers.go
Original file line number Diff line number Diff line change
Expand Up @@ -131,6 +131,13 @@ func (m *authorityEndpoint) openIDConfigurationEndpoint(ctx context.Context, aut
return "", err
}
return resp.TenantDiscoveryEndpoint, nil
} else if authorityInfo.Region != "" {
resp, err := m.rest.Authority().AADInstanceDiscovery(ctx, authorityInfo)
if err != nil {
return "", err
}
return resp.TenantDiscoveryEndpoint, nil

}

return authorityInfo.CanonicalAuthorityURI + "v2.0/.well-known/openid-configuration", nil
Expand Down
2 changes: 1 addition & 1 deletion apps/internal/version/version.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,4 +5,4 @@
package version

// Version is the version of this client package that is communicated to the server.
const Version = "0.4.0"
const Version = "0.5.0"
1 change: 1 addition & 0 deletions apps/public/public.go
Original file line number Diff line number Diff line change
Expand Up @@ -362,6 +362,7 @@ func (pca Client) browserLogin(ctx context.Context, redirectURI *url.URL, params
return interactiveAuthResult{}, err
}
defer srv.Shutdown()
params.Scopes = accesstokens.AppendDefaultScopes(params)
authURL, err := pca.base.AuthCodeURL(ctx, params.ClientID, srv.Addr, params.Scopes, params)
if err != nil {
return interactiveAuthResult{}, err
Expand Down

0 comments on commit f7df979

Please sign in to comment.