config.go raw

   1  package 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  
  17  import (
  18  	"errors"
  19  	"fmt"
  20  	"net/url"
  21  )
  22  
  23  const (
  24  	activeDirectoryEndpointTemplate = "%s/oauth2/%s%s"
  25  )
  26  
  27  // OAuthConfig represents the endpoints needed
  28  // in OAuth operations
  29  type 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.
  37  func (oac OAuthConfig) IsZero() bool {
  38  	return oac == OAuthConfig{}
  39  }
  40  
  41  func 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
  49  func 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.
  56  func 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.
  98  type MultiTenantOAuthConfig interface {
  99  	PrimaryTenant() *OAuthConfig
 100  	AuxiliaryTenants() []*OAuthConfig
 101  }
 102  
 103  // OAuthOptions contains optional OAuthConfig creation arguments.
 104  type OAuthOptions struct {
 105  	APIVersion string
 106  }
 107  
 108  func (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.
 117  func 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  
 140  type multiTenantOAuthConfig struct {
 141  	// first config in the slice is the primary tenant
 142  	cfgs []*OAuthConfig
 143  }
 144  
 145  func (m multiTenantOAuthConfig) PrimaryTenant() *OAuthConfig {
 146  	return m.cfgs[0]
 147  }
 148  
 149  func (m multiTenantOAuthConfig) AuxiliaryTenants() []*OAuthConfig {
 150  	return m.cfgs[1:]
 151  }
 152