Skip to content

Commit 9b37515

Browse files
committed
wip
1 parent 99d8033 commit 9b37515

17 files changed

+490
-2
lines changed

credentials.go

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
package entraid
2+
3+
import (
4+
auth "github.com/redis/go-redis/v9/auth"
5+
)
6+
7+
// authCredentials implements the auth.Credentials interface.
8+
var _ auth.Credentials = (*authCredentials)(nil)
9+
10+
// authCredentials represents the authentication credentials used to access the Entraid API.
11+
// It contains the username, password, and raw credentials.
12+
// The authCredentials struct is used to store the authentication credentials
13+
type authCredentials struct {
14+
username string
15+
password string
16+
rawCredentials string
17+
}
18+
19+
// BasicAuth returns the username and password for basic authentication.
20+
func (a *authCredentials) BasicAuth() (username string, password string) {
21+
return a.username, a.password
22+
}
23+
24+
// RawCredentials returns the raw credentials for authentication.
25+
func (a *authCredentials) RawCredentials() string {
26+
return a.rawCredentials
27+
}

credentials_provider.go

Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
package entraid
2+
3+
import (
4+
"fmt"
5+
6+
"github.com/redis/go-redis/v9/auth"
7+
)
8+
9+
// entraidCredentialsProvider implements the auth.StreamingCredentialsProvider interface.
10+
var _ auth.StreamingCredentialsProvider = (*entraidCredentialsProvider)(nil)
11+
12+
// entraidCredentialsProvider is a struct that implements the CredentialProvider interface.
13+
type entraidCredentialsProvider struct {
14+
options CredentialsProviderOptions
15+
16+
tokenManager TokenManager
17+
cancelTokenManager cancelFunc
18+
19+
listeners []auth.CredentialsListener
20+
}
21+
22+
// onTokenNext is a method that is called when the token manager receives a new token.
23+
func (e *entraidCredentialsProvider) onTokenNext(token *Token) {
24+
// Notify all listeners with the new token.
25+
for _, listener := range e.listeners {
26+
listener.OnNext(&authCredentials{
27+
username: token.Username,
28+
password: token.Password,
29+
rawCredentials: token.RawToken,
30+
})
31+
}
32+
}
33+
34+
// onError is a method that is called when the token manager encounters an error.
35+
// It notifies all listeners with the error.
36+
func (e *entraidCredentialsProvider) onTokenError(err error) {
37+
// Notify all listeners with the error.
38+
for _, listener := range e.listeners {
39+
listener.OnError(err)
40+
}
41+
}
42+
43+
// Subscribe subscribes to the credentials provider and returns a channel that will receive updates.
44+
// The first response is blocking, then data will notify the listener.
45+
// The listener will be notified with the credentials when they are available.
46+
// The listener will be notified with an error if there is an error obtaining the credentials.
47+
// The caller can cancel the subscription by calling the cancel function which is the second return value.
48+
func (e *entraidCredentialsProvider) Subscribe(listener auth.CredentialsListener) (auth.Credentials, auth.CancelProviderFunc, error) {
49+
// Check if the listener is already in the list of listeners.
50+
alreadySubscribed := false
51+
for _, l := range e.listeners {
52+
if l == listener {
53+
alreadySubscribed = true
54+
break
55+
}
56+
}
57+
58+
if !alreadySubscribed {
59+
// Get the token from the identity provider.
60+
e.listeners = append(e.listeners, listener)
61+
}
62+
63+
token, err := e.tokenManager.GetToken()
64+
if err != nil {
65+
listener.OnError(err)
66+
return nil, nil, err
67+
}
68+
69+
// Create a new credentials object.
70+
credentials := &authCredentials{
71+
username: token.Username,
72+
password: token.Password,
73+
rawCredentials: token.RawToken,
74+
}
75+
76+
// Notify the listener with the credentials.
77+
listener.OnNext(credentials)
78+
79+
cancel := func() error {
80+
// Remove the listener from the list of listeners.
81+
for i, l := range e.listeners {
82+
if l == listener {
83+
e.listeners = append(e.listeners[:i], e.listeners[i+1:]...)
84+
break
85+
}
86+
}
87+
if len(e.listeners) == 0 {
88+
e.cancelTokenManager()
89+
}
90+
return nil
91+
}
92+
93+
return credentials, cancel, nil
94+
}
95+
96+
// newCredentialsProvider creates a new credentials provider.
97+
// It takes a TokenManager and CredentialProviderOptions as arguments and returns a StreamingCredentialsProvider interface.
98+
// The TokenManager is used to obtain the token, and the CredentialProviderOptions contains options for the credentials provider.
99+
// The credentials provider is responsible for managing the credentials and refreshing them when necessary.
100+
// It returns an error if the token manager cannot be started.
101+
func newCredentialsProvider(tokenManager TokenManager, options CredentialProviderOptions) (auth.StreamingCredentialsProvider, error) {
102+
cp := &entraidCredentialsProvider{
103+
tokenManager: tokenManager,
104+
options: options,
105+
}
106+
cancelTokenManager, err := cp.tokenManager.Start(tokenListenerFromCP(cp))
107+
if err != nil {
108+
return nil, fmt.Errorf("couldn't start token manager: %w", err)
109+
}
110+
cp.cancelTokenManager = cancelTokenManager
111+
return cp, nil
112+
}
113+
114+
type entraidTokenListener struct {
115+
onTokenNext func(token *Token)
116+
onTokenError func(err error)
117+
}
118+
119+
func tokenListenerFromCP(cp *entraidCredentialsProvider) *entraidTokenListener {
120+
return &entraidTokenListener{
121+
onTokenNext: cp.onTokenNext,
122+
onTokenError: cp.onTokenError,
123+
}
124+
}
125+
126+
func (l *entraidTokenListener) OnTokenNext(token *Token) {
127+
l.onTokenNext(token)
128+
}
129+
130+
func (l *entraidTokenListener) OnTokenError(err error) {
131+
l.onTokenError(err)
132+
}

entraid.go

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1 @@
1+
package entraid

errors.go

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
package entraid
2+
3+
import "fmt"
4+
5+
var ErrNotImplemented = fmt.Errorf("credentials provider not implemented")

examples/example_test.go

Lines changed: 16 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,16 @@
1+
package examples_test
2+
3+
import (
4+
"context"
5+
"fmt"
6+
7+
"github.com/redis/go-redis/v9"
8+
)
9+
10+
func ExampleEstablishRedisConn() {
11+
rdb := redis.NewUniversalClient(&redis.UniversalOptions{
12+
Addrs: []string{"localhost:6379"},
13+
})
14+
fmt.Println(rdb.Ping(context.Background()).String())
15+
// Output: ping: PONG
16+
}

examples/go.mod

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
module examples
2+
3+
go 1.23
4+
5+
require github.com/redis/go-redis/v9 v9.7.1
6+
7+
require (
8+
github.com/cespare/xxhash/v2 v2.2.0 // indirect
9+
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f // indirect
10+
)

examples/go.sum

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
github.com/bsm/ginkgo/v2 v2.12.0 h1:Ny8MWAHyOepLGlLKYmXG4IEkioBysk6GpaRTLC8zwWs=
2+
github.com/bsm/ginkgo/v2 v2.12.0/go.mod h1:SwYbGRRDovPVboqFv0tPTcG1sN61LM1Z4ARdbAV9g4c=
3+
github.com/bsm/gomega v1.27.10 h1:yeMWxP2pV2fG3FgAODIY8EiRE3dy0aeFYt4l7wh6yKA=
4+
github.com/bsm/gomega v1.27.10/go.mod h1:JyEr/xRbxbtgWNi8tIEVPUYZ5Dzef52k01W3YH0H+O0=
5+
github.com/cespare/xxhash/v2 v2.2.0 h1:DC2CZ1Ep5Y4k3ZQ899DldepgrayRUGE6BBZ/cd9Cj44=
6+
github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
7+
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f h1:lO4WD4F/rVNCu3HqELle0jiPLLBs70cWOduZpkS1E78=
8+
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
9+
github.com/redis/go-redis/v9 v9.7.1 h1:4LhKRCIduqXqtvCUlaq9c8bdHOkICjDMrr1+Zb3osAc=
10+
github.com/redis/go-redis/v9 v9.7.1/go.mod h1:f6zhXITC7JUJIlPEiBOTXxJgPLdZcA93GewI7inzyWw=

go.mod

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,4 +2,8 @@ module github.com/redis-developer/go-redis-entraid
22

33
go 1.18
44

5+
replace github.com/redis/go-redis/v9 => /Users/nedyalko.dyakov/go-redis/
56

7+
require github.com/redis/go-redis/v9 v9.7.1
8+
9+
require github.com/AzureAD/microsoft-authentication-library-for-go v1.4.1 // indirect

go.sum

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,5 @@
1+
github.com/AzureAD/microsoft-authentication-library-for-go v1.4.1 h1:8BKxhZZLX/WosEeoCvWysmKUscfa9v8LIPEEU0JjE2o=
2+
github.com/AzureAD/microsoft-authentication-library-for-go v1.4.1/go.mod h1:wP83P5OoQ5p6ip3ScPr0BAq0BvuPAvacpEuSzyouqAI=
3+
github.com/cespare/xxhash/v2 v2.2.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs=
4+
github.com/dgryski/go-rendezvous v0.0.0-20200823014737-9f7001d12a5f/go.mod h1:cuUVRXasLTGF7a8hSLbxyZXjz+1KgoB3wDUb6vlszIc=
5+
github.com/redis/go-redis/v9 v9.7.1/go.mod h1:f6zhXITC7JUJIlPEiBOTXxJgPLdZcA93GewI7inzyWw=

identity_provider.go

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
package entraid
2+
3+
type IdentityProvider interface {
4+
// requestToken requests a token from the identity provider.
5+
requestToken() (string, error)
6+
}

0 commit comments

Comments
 (0)