Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Popesku/add validation token #185

Merged
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions core/license.go
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,9 @@ Please check if https://qodana.cloud/ is accessible from your environment.
If you encounter any issues, please contact us at [email protected].
Or use our issue tracker at https://jb.gg/qodana-issue`

const invalidTokenMessage = `
Token validation failed. Please ensure that the token provided through the QODANA_TOKEN environment variable is correct`

const declinedTokenErrorMessage = `
License verification failed. Please ensure that the token provided through the QODANA_TOKEN
environment variable is correct and that you have a valid license.
Expand Down
45 changes: 40 additions & 5 deletions core/options.go
Original file line number Diff line number Diff line change
Expand Up @@ -116,21 +116,41 @@ func (o *QodanaOptions) id() string {
return o._id
}

// ValidateToken checks if QODANA_TOKEN is set in CLI args, or environment or the system keyring, returns it's value.
func (o *QodanaOptions) ValidateToken(refresh bool) string {
func (o *QodanaOptions) loadToken(refresh bool) string {
tokenFetchers := []func(bool) string{
func(_ bool) string { return o.getTokenFromCliArgs() },
func(_ bool) string { return o.getTokenFromEnv() },
o.getTokenFromKeychain,
func(_ bool) string { return o.getTokenFromUserInput() },
}

for _, fetcher := range tokenFetchers {
if token := fetcher(refresh); token != "" {
return token
}
}
return ""
}

func (o *QodanaOptions) getTokenFromCliArgs() string {
tokenFromCliArgs := o.getenv(qodanaToken)
if tokenFromCliArgs != "" {
log.Debug("Loaded token from CLI args environment")
return tokenFromCliArgs
}
return ""
}

func (o *QodanaOptions) getTokenFromEnv() string {
tokenFromEnv := os.Getenv(qodanaToken)
if tokenFromEnv != "" {
o.setenv(qodanaToken, os.Getenv(qodanaToken))
log.Debug("Loaded token from the environment variable")
return tokenFromEnv
}
return ""
}

func (o *QodanaOptions) getTokenFromKeychain(refresh bool) string {
log.Debugf("project id: %s", o.id())
tokenFromKeychain, err := getCloudToken(o.id())
if err == nil && tokenFromKeychain != "" {
Expand All @@ -146,20 +166,35 @@ func (o *QodanaOptions) ValidateToken(refresh bool) string {
return tokenFromKeychain
}
}
return ""
}

func (o *QodanaOptions) getTokenFromUserInput() string {
if IsInteractive() {
WarningMessage(emptyTokenMessage)
token := setupToken(o.ProjectDir, o.id())
if token != "" {
log.Debugf("Loaded token from the user input, saved to the system keyring with id %s", o.id())
o.setenv(qodanaToken, token)
return token
}
}

return ""
}

// ValidateToken checks if QODANA_TOKEN is set in CLI args, or environment or the system keyring, returns it's value.
func (o *QodanaOptions) ValidateToken(refresh bool) string {
token := o.loadToken(refresh)
client := NewQodanaClient()
if projectName := client.validateToken(token); projectName == "" {
WarningMessage(invalidTokenMessage)
} else {
SuccessMessage("Linked project name: %s", projectName)
o.setenv(qodanaToken, token)
return token
}
return token
}

func (o *QodanaOptions) getQodanaSystemDir() string {
if o.CacheDir != "" {
return filepath.Dir(o.CacheDir)
Expand Down
102 changes: 102 additions & 0 deletions core/qodana_cloud_client.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
package core

import (
"bytes"
"encoding/json"
"io"
"net/http"
"time"
)

const (
baseUrl = "https://api.qodana.cloud"
maxNumberOfRetries = 3
waitTimeout = time.Second * 30
requestTimeout = time.Second * 30
)

type QodanaClient struct {
httpClient *http.Client
}

func NewQodanaClient() *QodanaClient {
return &QodanaClient{
httpClient: &http.Client{
Timeout: requestTimeout,
},
}
}

type Success struct {
Data map[string]interface{}
}

type RequestResult interface {
isRequestResult()
}

type APIError struct {
StatusCode int
Message string
}

type RequestError struct {
Err error
}

func (Success) isRequestResult() {}
func (APIError) isRequestResult() {}
func (RequestError) isRequestResult() {}

func (client *QodanaClient) validateToken(token string) interface{} {
result := client.GetProjectByToken(token)
switch v := result.(type) {
case Success:
return v.Data["name"]
default:
return ""
}
}

func (client *QodanaClient) GetProjectByToken(token string) RequestResult {
return client.doRequest("/v1/projects", token, "GET", nil, nil)
}

func (client *QodanaClient) doRequest(path, token, method string, headers map[string]string, body []byte) RequestResult {
url := baseUrl + path
var resp *http.Response
var err error

for i := 0; i < maxNumberOfRetries; i++ {
req, err := http.NewRequest(method, url, bytes.NewBuffer(body))
if err != nil {
return RequestError{Err: err}
}

req.Header.Set("Authorization", "Bearer "+token)
req.Header.Set("Content-Type", "application/json")
for key, value := range headers {
req.Header.Set(key, value)
}

resp, err = client.httpClient.Do(req)
if err == nil {
break
}
time.Sleep(waitTimeout)
}
if err != nil {
return RequestError{Err: err}
}
defer resp.Body.Close()

responseBody, _ := io.ReadAll(resp.Body)
if resp.StatusCode >= http.StatusOK && resp.StatusCode < http.StatusMultipleChoices {
var data map[string]interface{}
if err := json.Unmarshal(responseBody, &data); err != nil {
return RequestError{Err: err}
}
return Success{Data: data}
}
return APIError{StatusCode: resp.StatusCode, Message: string(responseBody)}
}
30 changes: 30 additions & 0 deletions core/validation_token_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
package core

import (
"net/http"
"testing"
)

func TestGetProjectByBadToken(t *testing.T) {
client := NewQodanaClient()
result := client.GetProjectByToken("https://www.jetbrains.com")
switch v := result.(type) {
case Success:
t.Errorf("Did not expect request error: %v", v)
case APIError:
if v.StatusCode > http.StatusBadRequest {
t.Errorf("Expected status code %d, got %d", http.StatusBadRequest, v.StatusCode)
}
case RequestError:
t.Errorf("Did not expect request error: %v", v)
default:
t.Error("Unknown result type")
}
}

func TestValidateToken(t *testing.T) {
client := NewQodanaClient()
if projectName := client.validateToken("kek"); projectName != "" {
t.Errorf("Problem")
}
}