Skip to content

Commit 49585a7

Browse files
committed
Identity Provider - Core Interface
1 parent 6c1490b commit 49585a7

11 files changed

+1466
-0
lines changed

identity/authority_configuration.go

Lines changed: 59 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,59 @@
1+
package identity
2+
3+
import "fmt"
4+
5+
const (
6+
// AuthorityTypeDefault is the default authority type.
7+
// This is used to specify the authority type when requesting a token.
8+
AuthorityTypeDefault = "default"
9+
// AuthorityTypeMultiTenant is the multi-tenant authority type.
10+
// This is used to specify the multi-tenant authority type when requesting a token.
11+
// This type of authority is used to authenticate the identity when requesting a token.
12+
AuthorityTypeMultiTenant = "multi-tenant"
13+
// AuthorityTypeCustom is the custom authority type.
14+
// This is used to specify the custom authority type when requesting a token.
15+
AuthorityTypeCustom = "custom"
16+
)
17+
18+
// AuthorityConfiguration represents the authority configuration for the identity provider.
19+
// It is used to configure the authority type and authority URL when requesting a token.
20+
type AuthorityConfiguration struct {
21+
// AuthorityType is the type of authority used to authenticate with the identity provider.
22+
// This can be either "default", "multi-tenant", or "custom".
23+
AuthorityType string
24+
25+
// Authority is the authority used to authenticate with the identity provider.
26+
// This is typically the URL of the identity provider.
27+
// For example, "https://login.microsoftonline.com/{tenantID}/v2.0"
28+
Authority string
29+
30+
// TenantID is the tenant ID of the identity provider.
31+
// This is used to identify the tenant when requesting a token.
32+
// This is typically the ID of the Azure Active Directory tenant.
33+
TenantID string
34+
}
35+
36+
// getAuthority returns the authority URL based on the authority type.
37+
// The authority type can be either "default", "multi-tenant", or "custom".
38+
func (a AuthorityConfiguration) getAuthority() (string, error) {
39+
if a.AuthorityType == "" {
40+
a.AuthorityType = AuthorityTypeDefault
41+
}
42+
43+
switch a.AuthorityType {
44+
case AuthorityTypeDefault:
45+
return "https://login.microsoftonline.com/common", nil
46+
case AuthorityTypeMultiTenant:
47+
if a.TenantID == "" {
48+
return "", fmt.Errorf("tenant ID is required when using multi-tenant authority type")
49+
}
50+
return fmt.Sprintf("https://login.microsoftonline.com/%s", a.TenantID), nil
51+
case AuthorityTypeCustom:
52+
if a.Authority == "" {
53+
return "", fmt.Errorf("authority is required when using custom authority type")
54+
}
55+
return a.Authority, nil
56+
default:
57+
return "", fmt.Errorf("invalid authority type: %s", a.AuthorityType)
58+
}
59+
}
Lines changed: 160 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,160 @@
1+
package identity
2+
3+
import (
4+
"testing"
5+
6+
"github.com/stretchr/testify/assert"
7+
)
8+
9+
func TestAuthorityConfiguration(t *testing.T) {
10+
t.Parallel()
11+
tests := []struct {
12+
name string
13+
authorityType string
14+
tenantID string
15+
authority string
16+
expected string
17+
expectError bool
18+
}{
19+
{
20+
name: "Default Authority",
21+
authorityType: AuthorityTypeDefault,
22+
expected: "https://login.microsoftonline.com/common",
23+
expectError: false,
24+
},
25+
{
26+
name: "Multi-Tenant Authority",
27+
authorityType: AuthorityTypeMultiTenant,
28+
tenantID: "12345",
29+
expected: "https://login.microsoftonline.com/12345",
30+
expectError: false,
31+
},
32+
{
33+
name: "Custom Authority",
34+
authorityType: AuthorityTypeCustom,
35+
authority: "https://custom-authority.com",
36+
expected: "https://custom-authority.com",
37+
expectError: false,
38+
},
39+
{
40+
name: "Invalid Authority Type",
41+
authorityType: "invalid",
42+
expectError: true,
43+
},
44+
{
45+
name: "Missing Tenant ID for Multi-Tenant",
46+
authorityType: AuthorityTypeMultiTenant,
47+
expectError: true,
48+
},
49+
{
50+
name: "Missing Authority for Custom",
51+
authorityType: AuthorityTypeCustom,
52+
expectError: true,
53+
},
54+
{
55+
name: "Default Authority Type with Tenant ID",
56+
authorityType: AuthorityTypeDefault,
57+
tenantID: "12345",
58+
expected: "https://login.microsoftonline.com/common",
59+
expectError: false,
60+
},
61+
}
62+
63+
for _, test := range tests {
64+
t.Run(test.name, func(t *testing.T) {
65+
ac := AuthorityConfiguration{
66+
AuthorityType: test.authorityType,
67+
TenantID: test.tenantID,
68+
Authority: test.authority,
69+
}
70+
result, err := ac.getAuthority()
71+
if test.expectError {
72+
assert.Error(t, err)
73+
} else {
74+
assert.NoError(t, err)
75+
assert.Equal(t, test.expected, result)
76+
}
77+
})
78+
}
79+
}
80+
81+
func TestAuthorityConfigurationDefault(t *testing.T) {
82+
t.Parallel()
83+
ac := AuthorityConfiguration{}
84+
result, err := ac.getAuthority()
85+
assert.NoError(t, err)
86+
assert.Equal(t, "https://login.microsoftonline.com/common", result)
87+
}
88+
89+
func TestAuthorityConfigurationMultiTenant(t *testing.T) {
90+
t.Parallel()
91+
ac := AuthorityConfiguration{
92+
AuthorityType: AuthorityTypeMultiTenant,
93+
TenantID: "12345",
94+
}
95+
result, err := ac.getAuthority()
96+
assert.NoError(t, err)
97+
assert.Equal(t, "https://login.microsoftonline.com/12345", result)
98+
}
99+
100+
func TestAuthorityConfigurationCustom(t *testing.T) {
101+
t.Parallel()
102+
ac := AuthorityConfiguration{
103+
AuthorityType: AuthorityTypeCustom,
104+
Authority: "https://custom-authority.com",
105+
}
106+
result, err := ac.getAuthority()
107+
assert.NoError(t, err)
108+
assert.Equal(t, "https://custom-authority.com", result)
109+
}
110+
111+
func TestAuthorityConfigurationInvalid(t *testing.T) {
112+
t.Parallel()
113+
ac := AuthorityConfiguration{
114+
AuthorityType: "invalid",
115+
}
116+
result, err := ac.getAuthority()
117+
assert.Error(t, err)
118+
assert.Equal(t, "", result)
119+
}
120+
121+
func TestAuthorityConfigurationMissingTenantID(t *testing.T) {
122+
t.Parallel()
123+
ac := AuthorityConfiguration{
124+
AuthorityType: AuthorityTypeMultiTenant,
125+
}
126+
result, err := ac.getAuthority()
127+
assert.Error(t, err)
128+
assert.Equal(t, "", result)
129+
}
130+
131+
func TestAuthorityConfigurationMissingAuthority(t *testing.T) {
132+
t.Parallel()
133+
ac := AuthorityConfiguration{
134+
AuthorityType: AuthorityTypeCustom,
135+
}
136+
result, err := ac.getAuthority()
137+
assert.Error(t, err)
138+
assert.Equal(t, "", result)
139+
}
140+
141+
func TestAuthorityConfigurationDefaultAuthorityType(t *testing.T) {
142+
t.Parallel()
143+
ac := AuthorityConfiguration{
144+
TenantID: "12345",
145+
}
146+
result, err := ac.getAuthority()
147+
assert.NoError(t, err)
148+
assert.Equal(t, "https://login.microsoftonline.com/common", result)
149+
}
150+
151+
func TestAuthorityConfigurationDefaultAuthorityTypeWithTenantID(t *testing.T) {
152+
t.Parallel()
153+
ac := AuthorityConfiguration{
154+
AuthorityType: AuthorityTypeDefault,
155+
TenantID: "12345",
156+
}
157+
result, err := ac.getAuthority()
158+
assert.NoError(t, err)
159+
assert.Equal(t, "https://login.microsoftonline.com/common", result)
160+
}
Lines changed: 77 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,77 @@
1+
package identity
2+
3+
import (
4+
"context"
5+
"fmt"
6+
7+
"github.com/Azure/azure-sdk-for-go/sdk/azcore"
8+
"github.com/Azure/azure-sdk-for-go/sdk/azcore/policy"
9+
"github.com/Azure/azure-sdk-for-go/sdk/azidentity"
10+
"github.com/redis-developer/go-redis-entraid/shared"
11+
)
12+
13+
// DefaultAzureIdentityProviderOptions represents the options for the DefaultAzureIdentityProvider.
14+
type DefaultAzureIdentityProviderOptions struct {
15+
// AzureOptions is the options used to configure the Azure identity provider.
16+
AzureOptions *azidentity.DefaultAzureCredentialOptions
17+
// Scopes is the list of scopes used to request a token from the identity provider.
18+
Scopes []string
19+
20+
// credFactory is a factory for creating the default Azure credential.
21+
// This is used for testing purposes, to allow mocking the credential creation.
22+
// If not provided, the default implementation - azidentity.NewDefaultAzureCredential will be used
23+
credFactory credFactory
24+
}
25+
26+
type credFactory interface {
27+
NewDefaultAzureCredential(options *azidentity.DefaultAzureCredentialOptions) (azureCredential, error)
28+
}
29+
30+
type azureCredential interface {
31+
GetToken(ctx context.Context, options policy.TokenRequestOptions) (azcore.AccessToken, error)
32+
}
33+
34+
type defaultCredFactory struct{}
35+
36+
func (d *defaultCredFactory) NewDefaultAzureCredential(options *azidentity.DefaultAzureCredentialOptions) (azureCredential, error) {
37+
return azidentity.NewDefaultAzureCredential(options)
38+
}
39+
40+
type DefaultAzureIdentityProvider struct {
41+
options *azidentity.DefaultAzureCredentialOptions
42+
credFactory credFactory
43+
scopes []string
44+
}
45+
46+
// NewDefaultAzureIdentityProvider creates a new DefaultAzureIdentityProvider.
47+
func NewDefaultAzureIdentityProvider(opts DefaultAzureIdentityProviderOptions) (*DefaultAzureIdentityProvider, error) {
48+
if opts.Scopes == nil {
49+
opts.Scopes = []string{RedisScopeDefault}
50+
}
51+
52+
return &DefaultAzureIdentityProvider{
53+
options: opts.AzureOptions,
54+
scopes: opts.Scopes,
55+
credFactory: opts.credFactory,
56+
}, nil
57+
}
58+
59+
// RequestToken requests a token from the Azure Default Identity provider.
60+
// It returns the token, the expiration time, and an error if any.
61+
func (a *DefaultAzureIdentityProvider) RequestToken() (shared.IdentityProviderResponse, error) {
62+
credFactory := a.credFactory
63+
if credFactory == nil {
64+
credFactory = &defaultCredFactory{}
65+
}
66+
cred, err := credFactory.NewDefaultAzureCredential(a.options)
67+
if err != nil {
68+
return nil, fmt.Errorf("failed to create default azure credential: %w", err)
69+
}
70+
71+
token, err := cred.GetToken(context.TODO(), policy.TokenRequestOptions{Scopes: a.scopes})
72+
if err != nil {
73+
return nil, fmt.Errorf("failed to get token: %w", err)
74+
}
75+
76+
return shared.NewIDPResponse(shared.ResponseTypeAccessToken, &token)
77+
}

0 commit comments

Comments
 (0)