Skip to content

Commit 0343a6e

Browse files
committed
Adds suport for token refreshing mechanism on token expiry
Signed-off-by: Harsh4902 <[email protected]>
1 parent 5f427d0 commit 0343a6e

File tree

2 files changed

+204
-29
lines changed

2 files changed

+204
-29
lines changed

pkg/config/localconfig.go

Lines changed: 38 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -14,6 +14,7 @@ type LocalConfig struct {
1414
Servers []Server `yaml:"servers"`
1515
Users []User `yaml:"users"`
1616
Instances []Instance `yaml:"instances"`
17+
Auths []Auth `yaml:"auths"`
1718
}
1819

1920
type ContextRef struct {
@@ -53,6 +54,12 @@ type Instance struct {
5354
Driver string `yaml:"driver"`
5455
}
5556

57+
type Auth struct {
58+
Server string
59+
ClientId string
60+
ClientSecret string
61+
}
62+
5663
// ReadLocalConfig loads up the local configuration file. Returns nil if config does not exist
5764
func ReadLocalConfig(path string) (*LocalConfig, error) {
5865
var err error
@@ -293,3 +300,34 @@ func (l *LocalConfig) RemoveInstance(instanceName string) bool {
293300
func (l *LocalConfig) IsEmpty() bool {
294301
return len(l.Servers) == 0
295302
}
303+
304+
func (l *LocalConfig) GetAuth(server string) (*Auth, error) {
305+
for _, a := range l.Auths {
306+
if a.Server == server {
307+
return &a, nil
308+
}
309+
}
310+
311+
return nil, fmt.Errorf("Auth for '%s' is undifined\n", server)
312+
}
313+
314+
func (l *LocalConfig) UpserAuth(auth Auth) {
315+
for i, a := range l.Auths {
316+
if a.Server == auth.Server {
317+
l.Auths[i] = auth
318+
return
319+
}
320+
}
321+
322+
l.Auths = append(l.Auths, auth)
323+
}
324+
325+
func (l *LocalConfig) RemoveAuth(server string) bool {
326+
for i, a := range l.Auths {
327+
if a.Server == server {
328+
l.Auths = append(l.Auths[:i], l.Auths[i+1:]...)
329+
return true
330+
}
331+
}
332+
return false
333+
}

pkg/connectors/microcks_client.go

Lines changed: 166 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -17,11 +17,14 @@ package connectors
1717

1818
import (
1919
"bytes"
20+
"context"
21+
"crypto/tls"
2022
"encoding/json"
21-
"errors"
23+
errs "errors"
2224
"fmt"
2325
"io"
2426
"io/ioutil"
27+
"log"
2528
"mime/multipart"
2629
"net/http"
2730
"net/url"
@@ -30,7 +33,11 @@ import (
3033
"strconv"
3134
"strings"
3235

36+
"github.com/coreos/go-oidc/v3/oidc"
37+
"github.com/golang-jwt/jwt/v4"
3338
"github.com/microcks/microcks-cli/pkg/config"
39+
"github.com/microcks/microcks-cli/pkg/errors"
40+
"golang.org/x/oauth2"
3441
)
3542

3643
var (
@@ -79,41 +86,79 @@ type OAuth2ClientContext struct {
7986
Scopes string `json:"scopes"`
8087
}
8188

89+
type ClientOptions struct {
90+
ServerAddr string
91+
Context string
92+
ConfigPath string
93+
AuthToken string
94+
InsecureTLS bool
95+
Verbose bool
96+
CaCertPaths string
97+
}
98+
8299
type microcksClient struct {
83-
APIURL *url.URL
84-
OAuthToken string
100+
ServerAddr string
101+
APIURL *url.URL
102+
AuthToken string
103+
CertFile *tls.Certificate
104+
InsecureTLS bool
105+
RefreshToken string
106+
Insecure bool
107+
Verbose bool
85108

86109
httpClient *http.Client
87110
}
88111

89-
// NewMicrocksClient build a new MicrocksClient implementation
90-
func NewMicrocksClient(apiURL string) MicrocksClient {
91-
mc := microcksClient{}
112+
func NewClient(opts ClientOptions) (MicrocksClient, error) {
113+
var c microcksClient
114+
localCfg, err := config.ReadLocalConfig(opts.ConfigPath)
115+
if err != nil {
116+
return nil, err
117+
}
118+
var ctxName string
92119

93-
if !strings.HasSuffix(apiURL, "/api/") {
94-
apiURL += "/api/"
120+
if localCfg != nil {
121+
configCtx, err := localCfg.ResolveContext(opts.Context)
122+
if err != nil {
123+
return nil, err
124+
}
125+
c.ServerAddr = configCtx.Server.Server
126+
c.Insecure = configCtx.Server.KeycloackEnable
127+
c.InsecureTLS = configCtx.Server.InsecureTLS
128+
c.AuthToken = configCtx.User.AuthToken
129+
c.RefreshToken = configCtx.User.RefreshToken
130+
131+
apiurl := configCtx.Server.Server
132+
133+
if !strings.HasSuffix(apiurl, "/api/") {
134+
apiurl += "/api/"
135+
}
136+
137+
u, err := url.Parse(apiurl)
138+
if err != nil {
139+
panic(err)
140+
}
141+
c.APIURL = u
142+
143+
ctxName = configCtx.Name
95144
}
96145

97-
u, err := url.Parse(apiURL)
98-
if err != nil {
99-
panic(err)
146+
if opts.Verbose {
147+
c.Verbose = opts.Verbose
100148
}
101-
mc.APIURL = u
102149

103-
if config.InsecureTLS || len(config.CaCertPaths) > 0 {
104-
tlsConfig := config.CreateTLSConfig()
105-
tr := &http.Transport{
106-
TLSClientConfig: tlsConfig,
150+
c.httpClient = &http.Client{}
151+
if localCfg != nil {
152+
err = c.refreshAuthToken(localCfg, ctxName, opts.ConfigPath)
153+
if err != nil {
154+
return nil, err
107155
}
108-
mc.httpClient = &http.Client{Transport: tr}
109-
} else {
110-
mc.httpClient = http.DefaultClient
111156
}
112-
return &mc
157+
return &c, nil
113158
}
114159

115-
func (mc *microcksClient) HttpClient() *http.Client {
116-
return mc.httpClient
160+
func (c *microcksClient) HttpClient() *http.Client {
161+
return c.httpClient
117162
}
118163

119164
func (c *microcksClient) GetKeycloakURL() (string, error) {
@@ -162,8 +207,100 @@ func (c *microcksClient) GetKeycloakURL() (string, error) {
162207
return "null", nil
163208
}
164209

210+
func (c *microcksClient) refreshAuthToken(localCfg *config.LocalConfig, ctxName, configPath string) error {
211+
if c.RefreshToken == "" {
212+
// If we have no refresh token, there's no point in doing anything
213+
return nil
214+
}
215+
configCtx, err := localCfg.ResolveContext(ctxName)
216+
if err != nil {
217+
return err
218+
}
219+
parser := jwt.NewParser(jwt.WithoutClaimsValidation())
220+
var claims jwt.RegisteredClaims
221+
_, _, err = parser.ParseUnverified(configCtx.User.AuthToken, &claims)
222+
if err != nil {
223+
return err
224+
}
225+
if claims.Valid() == nil {
226+
// token is still valid
227+
return nil
228+
}
229+
230+
log.Printf("Auth token no longer valid. Refreshing")
231+
auth, err := localCfg.GetAuth(configCtx.Server.Server)
232+
if err != nil {
233+
return err
234+
}
235+
authToken, refreshToken, err := c.redeemRefreshToken(*auth)
236+
if err != nil {
237+
return err
238+
}
239+
c.AuthToken = authToken
240+
c.RefreshToken = refreshToken
241+
localCfg.UpsertUser(config.User{
242+
Name: ctxName,
243+
AuthToken: authToken,
244+
RefreshToken: refreshToken,
245+
})
246+
err = config.WriteLocalConfig(*localCfg, configPath)
247+
if err != nil {
248+
return err
249+
}
250+
return nil
251+
}
252+
253+
func (c *microcksClient) redeemRefreshToken(auth config.Auth) (string, string, error) {
254+
keyCloakUrl, err := c.GetKeycloakURL()
255+
errors.CheckError(err)
256+
kc := NewKeycloakClient(keyCloakUrl, "", "")
257+
oauth2Conf, err := kc.GetOIDCConfig()
258+
errors.CheckError(err)
259+
oauth2Conf.ClientID = auth.ClientId
260+
oauth2Conf.ClientSecret = auth.ClientSecret
261+
262+
httpClient := c.httpClient
263+
ctx := oidc.ClientContext(context.Background(), httpClient)
264+
265+
t := &oauth2.Token{
266+
RefreshToken: c.RefreshToken,
267+
}
268+
token, err := oauth2Conf.TokenSource(ctx, t).Token()
269+
if err != nil {
270+
return "", "", err
271+
}
272+
273+
return token.AccessToken, token.RefreshToken, nil
274+
}
275+
276+
// NewMicrocksClient builds a new headless MicrocksClient without any authtoken and all for general purposes
277+
func NewMicrocksClient(apiURL string) MicrocksClient {
278+
mc := microcksClient{}
279+
280+
if !strings.HasSuffix(apiURL, "/api/") {
281+
apiURL += "/api/"
282+
}
283+
284+
u, err := url.Parse(apiURL)
285+
if err != nil {
286+
panic(err)
287+
}
288+
mc.APIURL = u
289+
290+
if config.InsecureTLS || len(config.CaCertPaths) > 0 {
291+
tlsConfig := config.CreateTLSConfig()
292+
tr := &http.Transport{
293+
TLSClientConfig: tlsConfig,
294+
}
295+
mc.httpClient = &http.Client{Transport: tr}
296+
} else {
297+
mc.httpClient = http.DefaultClient
298+
}
299+
return &mc
300+
}
301+
165302
func (c *microcksClient) SetOAuthToken(oauthToken string) {
166-
c.OAuthToken = oauthToken
303+
c.AuthToken = oauthToken
167304
}
168305

169306
func (c *microcksClient) CreateTestResult(serviceID string, testEndpoint string, runnerType string, secretName string, timeout int64, filteredOperations string, operationsHeaders string, oAuth2Context string) (string, error) {
@@ -199,7 +336,7 @@ func (c *microcksClient) CreateTestResult(serviceID string, testEndpoint string,
199336

200337
req.Header.Set("Content-Type", "application/json; charset=utf-8")
201338
req.Header.Set("Accept", "application/json")
202-
req.Header.Set("Authorization", "Bearer "+c.OAuthToken)
339+
req.Header.Set("Authorization", "Bearer "+c.AuthToken)
203340

204341
// Dump request if verbose required.
205342
config.DumpRequestIfRequired("Microcks for creating test", req, true)
@@ -238,7 +375,7 @@ func (c *microcksClient) GetTestResult(testResultID string) (*TestResultSummary,
238375
}
239376

240377
req.Header.Set("Accept", "application/json")
241-
req.Header.Set("Authorization", "Bearer "+c.OAuthToken)
378+
req.Header.Set("Authorization", "Bearer "+c.AuthToken)
242379

243380
// Dump request if verbose required.
244381
config.DumpRequestIfRequired("Microcks for getting status", req, false)
@@ -300,7 +437,7 @@ func (c *microcksClient) UploadArtifact(specificationFilePath string, mainArtifa
300437
return "", err
301438
}
302439
req.Header.Set("Content-Type", writer.FormDataContentType())
303-
req.Header.Set("Authorization", "Bearer "+c.OAuthToken)
440+
req.Header.Set("Authorization", "Bearer "+c.AuthToken)
304441

305442
// Dump request if verbose required.
306443
config.DumpRequestIfRequired("Microcks for uploading artifact", req, true)
@@ -321,7 +458,7 @@ func (c *microcksClient) UploadArtifact(specificationFilePath string, mainArtifa
321458

322459
// Raise exception if not created.
323460
if resp.StatusCode != 201 {
324-
return "", errors.New(string(respBody))
461+
return "", errs.New(string(respBody))
325462
}
326463

327464
return string(respBody), err
@@ -354,7 +491,7 @@ func (c *microcksClient) DownloadArtifact(artifactURL string, mainArtifact bool,
354491
return "", err
355492
}
356493
req.Header.Set("Content-Type", writer.FormDataContentType())
357-
req.Header.Set("Authorization", "Bearer "+c.OAuthToken)
494+
req.Header.Set("Authorization", "Bearer "+c.AuthToken)
358495

359496
// Dump request if verbose required.
360497
config.DumpRequestIfRequired("Microcks for uploading artifact", req, true)
@@ -375,7 +512,7 @@ func (c *microcksClient) DownloadArtifact(artifactURL string, mainArtifact bool,
375512

376513
// Raise exception if not created.
377514
if resp.StatusCode != 201 {
378-
return "", errors.New(string(respBody))
515+
return "", errs.New(string(respBody))
379516
}
380517

381518
return string(respBody), err

0 commit comments

Comments
 (0)