1package adal 2 3// Copyright 2017 Microsoft Corporation 4// 5// Licensed under the Apache License, Version 2.0 (the "License"); 6// you may not use this file except in compliance with the License. 7// You may obtain a copy of the License at 8// 9// http://www.apache.org/licenses/LICENSE-2.0 10// 11// Unless required by applicable law or agreed to in writing, software 12// distributed under the License is distributed on an "AS IS" BASIS, 13// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 14// See the License for the specific language governing permissions and 15// limitations under the License. 16 17import ( 18 "errors" 19 "fmt" 20 "net/url" 21) 22 23const ( 24 activeDirectoryEndpointTemplate = "%s/oauth2/%s%s" 25) 26 27// OAuthConfig represents the endpoints needed 28// in OAuth operations 29type OAuthConfig struct { 30 AuthorityEndpoint url.URL `json:"authorityEndpoint"` 31 AuthorizeEndpoint url.URL `json:"authorizeEndpoint"` 32 TokenEndpoint url.URL `json:"tokenEndpoint"` 33 DeviceCodeEndpoint url.URL `json:"deviceCodeEndpoint"` 34} 35 36// IsZero returns true if the OAuthConfig object is zero-initialized. 37func (oac OAuthConfig) IsZero() bool { 38 return oac == OAuthConfig{} 39} 40 41func validateStringParam(param, name string) error { 42 if len(param) == 0 { 43 return fmt.Errorf("parameter '" + name + "' cannot be empty") 44 } 45 return nil 46} 47 48// NewOAuthConfig returns an OAuthConfig with tenant specific urls 49func NewOAuthConfig(activeDirectoryEndpoint, tenantID string) (*OAuthConfig, error) { 50 apiVer := "1.0" 51 return NewOAuthConfigWithAPIVersion(activeDirectoryEndpoint, tenantID, &apiVer) 52} 53 54// NewOAuthConfigWithAPIVersion returns an OAuthConfig with tenant specific urls. 55// If apiVersion is not nil the "api-version" query parameter will be appended to the endpoint URLs with the specified value. 56func NewOAuthConfigWithAPIVersion(activeDirectoryEndpoint, tenantID string, apiVersion *string) (*OAuthConfig, error) { 57 if err := validateStringParam(activeDirectoryEndpoint, "activeDirectoryEndpoint"); err != nil { 58 return nil, err 59 } 60 api := "" 61 // it's legal for tenantID to be empty so don't validate it 62 if apiVersion != nil { 63 if err := validateStringParam(*apiVersion, "apiVersion"); err != nil { 64 return nil, err 65 } 66 api = fmt.Sprintf("?api-version=%s", *apiVersion) 67 } 68 u, err := url.Parse(activeDirectoryEndpoint) 69 if err != nil { 70 return nil, err 71 } 72 authorityURL, err := u.Parse(tenantID) 73 if err != nil { 74 return nil, err 75 } 76 authorizeURL, err := u.Parse(fmt.Sprintf(activeDirectoryEndpointTemplate, tenantID, "authorize", api)) 77 if err != nil { 78 return nil, err 79 } 80 tokenURL, err := u.Parse(fmt.Sprintf(activeDirectoryEndpointTemplate, tenantID, "token", api)) 81 if err != nil { 82 return nil, err 83 } 84 deviceCodeURL, err := u.Parse(fmt.Sprintf(activeDirectoryEndpointTemplate, tenantID, "devicecode", api)) 85 if err != nil { 86 return nil, err 87 } 88 89 return &OAuthConfig{ 90 AuthorityEndpoint: *authorityURL, 91 AuthorizeEndpoint: *authorizeURL, 92 TokenEndpoint: *tokenURL, 93 DeviceCodeEndpoint: *deviceCodeURL, 94 }, nil 95} 96 97// MultiTenantOAuthConfig provides endpoints for primary and aulixiary tenant IDs. 98type MultiTenantOAuthConfig interface { 99 PrimaryTenant() *OAuthConfig 100 AuxiliaryTenants() []*OAuthConfig 101} 102 103// OAuthOptions contains optional OAuthConfig creation arguments. 104type OAuthOptions struct { 105 APIVersion string 106} 107 108func (c OAuthOptions) apiVersion() string { 109 if c.APIVersion != "" { 110 return fmt.Sprintf("?api-version=%s", c.APIVersion) 111 } 112 return "1.0" 113} 114 115// NewMultiTenantOAuthConfig creates an object that support multitenant OAuth configuration. 116// See https://docs.microsoft.com/en-us/azure/azure-resource-manager/authenticate-multi-tenant for more information. 117func NewMultiTenantOAuthConfig(activeDirectoryEndpoint, primaryTenantID string, auxiliaryTenantIDs []string, options OAuthOptions) (MultiTenantOAuthConfig, error) { 118 if len(auxiliaryTenantIDs) == 0 || len(auxiliaryTenantIDs) > 3 { 119 return nil, errors.New("must specify one to three auxiliary tenants") 120 } 121 mtCfg := multiTenantOAuthConfig{ 122 cfgs: make([]*OAuthConfig, len(auxiliaryTenantIDs)+1), 123 } 124 apiVer := options.apiVersion() 125 pri, err := NewOAuthConfigWithAPIVersion(activeDirectoryEndpoint, primaryTenantID, &apiVer) 126 if err != nil { 127 return nil, fmt.Errorf("failed to create OAuthConfig for primary tenant: %v", err) 128 } 129 mtCfg.cfgs[0] = pri 130 for i := range auxiliaryTenantIDs { 131 aux, err := NewOAuthConfig(activeDirectoryEndpoint, auxiliaryTenantIDs[i]) 132 if err != nil { 133 return nil, fmt.Errorf("failed to create OAuthConfig for tenant '%s': %v", auxiliaryTenantIDs[i], err) 134 } 135 mtCfg.cfgs[i+1] = aux 136 } 137 return mtCfg, nil 138} 139 140type multiTenantOAuthConfig struct { 141 // first config in the slice is the primary tenant 142 cfgs []*OAuthConfig 143} 144 145func (m multiTenantOAuthConfig) PrimaryTenant() *OAuthConfig { 146 return m.cfgs[0] 147} 148 149func (m multiTenantOAuthConfig) AuxiliaryTenants() []*OAuthConfig { 150 return m.cfgs[1:] 151} 152