@@ -17,11 +17,14 @@ package connectors
1717
1818import (
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
3643var (
@@ -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+
8299type 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
119164func (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+
165302func (c * microcksClient ) SetOAuthToken (oauthToken string ) {
166- c .OAuthToken = oauthToken
303+ c .AuthToken = oauthToken
167304}
168305
169306func (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