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