-
Notifications
You must be signed in to change notification settings - Fork 4
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
- Loading branch information
1 parent
8ad9d5d
commit c600323
Showing
11 changed files
with
1,067 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,142 @@ | ||
// Copyright 2024 BeyondTrust. All rights reserved. | ||
// Package authentication implements functions to call Beyondtrust Secret Safe API. | ||
// Unit tests for authentication package. | ||
package authentication | ||
|
||
import ( | ||
"go-client-library-passwordsafe/api/entities" | ||
"go-client-library-passwordsafe/api/utils" | ||
"log" | ||
"os" | ||
"reflect" | ||
|
||
"net/http" | ||
"net/http/httptest" | ||
"testing" | ||
) | ||
|
||
type UserTestConfig struct { | ||
name string | ||
server *httptest.Server | ||
response *entities.SignApinResponse | ||
} | ||
|
||
type GetTokenConfig struct { | ||
name string | ||
server *httptest.Server | ||
response string | ||
} | ||
|
||
type GetPasswordSafeAuthenticationConfig struct { | ||
name string | ||
server *httptest.Server | ||
response *entities.SignApinResponse | ||
} | ||
|
||
var logger = log.New(os.Stdout, "DEBUG: ", log.Ldate|log.Ltime) | ||
var httpClient, _ = utils.GetHttpClient(5, true, "", "") | ||
var authenticate, _ = Authenticate(httpClient, "https://fake.api.com:443/BeyondTrust/api/public/v3/", "fakeone_a654+9sdf7+8we4f", "fakeone_aasd156465sfdef", *logger) | ||
|
||
func TestSignOut(t *testing.T) { | ||
|
||
testConfig := UserTestConfig{ | ||
name: "TestSignOut", | ||
server: httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { | ||
w.Write([]byte(``)) | ||
})), | ||
response: nil, | ||
} | ||
|
||
err := authenticate.SignOut(testConfig.server.URL) | ||
if err != nil { | ||
t.Errorf("Test case Failed: %v", err) | ||
} | ||
} | ||
|
||
func TestSignAppin(t *testing.T) { | ||
|
||
testConfig := UserTestConfig{ | ||
name: "TestSignAppin", | ||
server: httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { | ||
w.Write([]byte(`{"UserId":1, "EmailAddress":"Felipe"}`)) | ||
})), | ||
response: &entities.SignApinResponse{ | ||
UserId: 1, | ||
EmailAddress: "Felipe", | ||
}, | ||
} | ||
|
||
response, err := authenticate.SignAppin(testConfig.server.URL+"/"+"TestSignAppin", "") | ||
|
||
if !reflect.DeepEqual(response, *testConfig.response) { | ||
t.Errorf("Test case Failed %v, %v", response, *testConfig.response) | ||
} | ||
|
||
if err != nil { | ||
t.Errorf("Test case Failed: %v", err) | ||
} | ||
} | ||
|
||
func TestGetToken(t *testing.T) { | ||
|
||
testConfig := GetTokenConfig{ | ||
name: "TestGetToken", | ||
server: httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { | ||
// Mocking Response accorging to the endpoint path | ||
switch r.URL.Path { | ||
|
||
case "/Auth/connect/token": | ||
w.Write([]byte(`{"access_token": "fake_token", "expires_in": 600, "token_type": "Bearer", "scope": "publicapi"}`)) | ||
|
||
default: | ||
http.NotFound(w, r) | ||
} | ||
})), | ||
response: "fake_token", | ||
} | ||
|
||
response, err := authenticate.GetToken(testConfig.server.URL+"/"+"Auth/connect/token", "", "") | ||
|
||
if response != testConfig.response { | ||
t.Errorf("Test case Failed %v, %v", response, testConfig.response) | ||
} | ||
|
||
if err != nil { | ||
t.Errorf("Test case Failed: %v", err) | ||
} | ||
} | ||
|
||
func TestGetPasswordSafeAuthentication(t *testing.T) { | ||
|
||
testConfig := GetPasswordSafeAuthenticationConfig{ | ||
name: "TestGetPasswordSafeAuthentication", | ||
server: httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { | ||
// Mocking Response according to the endpoint path | ||
switch r.URL.Path { | ||
|
||
case "/Auth/connect/token": | ||
w.Write([]byte(`{"access_token": "fake_token", "expires_in": 600, "token_type": "Bearer", "scope": "publicapi"}`)) | ||
|
||
case "/Auth/SignAppIn": | ||
w.Write([]byte(`{"UserId":1, "EmailAddress":"Felipe"}`)) | ||
|
||
default: | ||
http.NotFound(w, r) | ||
} | ||
})), | ||
response: &entities.SignApinResponse{ | ||
UserId: 1, | ||
EmailAddress: "Felipe", | ||
}, | ||
} | ||
authenticate.ApiUrl = testConfig.server.URL + "/" | ||
response, err := authenticate.GetPasswordSafeAuthentication() | ||
|
||
if !reflect.DeepEqual(response, *testConfig.response) { | ||
t.Errorf("Test case Failed %v, %v", response, *testConfig.response) | ||
} | ||
|
||
if err != nil { | ||
t.Errorf("Test case Failed: %v", err) | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,232 @@ | ||
// Copyright 2024 BeyondTrust. All rights reserved. | ||
// Package client implements functions to call Beyondtrust Secret Safe API. | ||
package authentication | ||
|
||
import ( | ||
"bytes" | ||
"encoding/json" | ||
"fmt" | ||
"go-client-library-passwordsafe/api/entities" | ||
"go-client-library-passwordsafe/api/utils" | ||
"io" | ||
|
||
"log" | ||
"net/http" | ||
"net/url" | ||
"time" | ||
|
||
backoff "github.com/cenkalti/backoff/v4" | ||
) | ||
|
||
type AuthenticationObj struct { | ||
ApiUrl string | ||
clientId string | ||
clientSecret string | ||
httpClient *http.Client | ||
ExponentialBackOff *backoff.ExponentialBackOff | ||
signApinResponse entities.SignApinResponse | ||
autenticationLogger log.Logger | ||
} | ||
|
||
// Authenticate in PS API | ||
func Authenticate(httpClient *http.Client, endpointUrl string, clientId string, clientSecret string, logger *log.Logger, maxElapsedTime int) (*AuthenticationObj, error) { | ||
|
||
backoffDefinition := backoff.NewExponentialBackOff() | ||
backoffDefinition.InitialInterval = 1 * time.Second | ||
backoffDefinition.MaxElapsedTime = time.Duration(maxElapsedTime) * time.Second | ||
backoffDefinition.RandomizationFactor = 0.5 | ||
|
||
// Client | ||
var client = httpClient | ||
|
||
authenticationObj := &AuthenticationObj{ | ||
ApiUrl: endpointUrl, | ||
httpClient: client, | ||
clientId: clientId, | ||
clientSecret: clientSecret, | ||
ExponentialBackOff: backoffDefinition, | ||
autenticationLogger: *logger, | ||
} | ||
|
||
return authenticationObj, nil | ||
} | ||
|
||
// GetPasswordSafeAuthentication call get token and sign app endpoint | ||
func (c *AuthenticationObj) GetPasswordSafeAuthentication() (entities.SignApinResponse, error) { | ||
accessToken, err := c.GetToken(fmt.Sprintf("%v%v", c.ApiUrl, "Auth/connect/token"), c.clientId, c.clientSecret) | ||
if err != nil { | ||
return entities.SignApinResponse{}, err | ||
} | ||
signApinResponse, err := c.SignAppin(fmt.Sprintf("%v%v", c.ApiUrl, "Auth/SignAppIn"), accessToken) | ||
if err != nil { | ||
return entities.SignApinResponse{}, err | ||
} | ||
return signApinResponse, nil | ||
} | ||
|
||
// GetToken get token from PS API | ||
func (c *AuthenticationObj) GetToken(endpointUrl string, clientId string, clientSecret string) (string, error) { | ||
|
||
params := url.Values{} | ||
params.Add("client_id", clientId) | ||
params.Add("client_secret", clientSecret) | ||
params.Add("grant_type", "client_credentials") | ||
|
||
var body io.ReadCloser | ||
var technicalError error | ||
var businessError error | ||
|
||
var buffer bytes.Buffer | ||
buffer.WriteString(params.Encode()) | ||
|
||
technicalError = backoff.Retry(func() error { | ||
body, technicalError, businessError, _ = c.CallSecretSafeAPI(endpointUrl, "POST", buffer, "GetToken", "") | ||
return technicalError | ||
}, c.ExponentialBackOff) | ||
|
||
if technicalError != nil { | ||
return "", technicalError | ||
} | ||
|
||
if businessError != nil { | ||
return "", businessError | ||
} | ||
|
||
bodyBytes, err := io.ReadAll(body) | ||
|
||
if err != nil { | ||
return "", err | ||
} | ||
|
||
responseString := string(bodyBytes) | ||
|
||
var data entities.GetTokenResponse | ||
|
||
err = json.Unmarshal([]byte(responseString), &data) | ||
if err != nil { | ||
utils.Logging("ERROR", err.Error(), c.autenticationLogger) | ||
return "", err | ||
} | ||
|
||
return data.AccessToken, nil | ||
|
||
} | ||
|
||
// SignAppin Signs app in PS API | ||
func (c *AuthenticationObj) SignAppin(endpointUrl string, accessToken string) (entities.SignApinResponse, error) { | ||
|
||
var userObject entities.SignApinResponse | ||
var body io.ReadCloser | ||
var technicalError error | ||
var businessError error | ||
var scode int | ||
|
||
err := backoff.Retry(func() error { | ||
body, technicalError, businessError, scode = c.CallSecretSafeAPI(endpointUrl, "POST", bytes.Buffer{}, "SignAppin", accessToken) | ||
if scode == 0 { | ||
return nil | ||
} | ||
return technicalError | ||
}, c.ExponentialBackOff) | ||
|
||
if err != nil { | ||
return entities.SignApinResponse{}, err | ||
} | ||
|
||
if scode == 0 { | ||
return entities.SignApinResponse{}, technicalError | ||
} | ||
|
||
if businessError != nil { | ||
return entities.SignApinResponse{}, businessError | ||
} | ||
|
||
defer body.Close() | ||
bodyBytes, err := io.ReadAll(body) | ||
if err != nil { | ||
return entities.SignApinResponse{}, err | ||
} | ||
|
||
err = json.Unmarshal(bodyBytes, &userObject) | ||
|
||
if err != nil { | ||
utils.Logging("ERROR", err.Error(), c.autenticationLogger) | ||
return entities.SignApinResponse{}, err | ||
} | ||
|
||
return userObject, nil | ||
} | ||
|
||
// SignOut signs out Secret Safe API. | ||
// Warn: should only be called one time for all data sources. | ||
func (c *AuthenticationObj) SignOut(url string) error { | ||
|
||
utils.Logging("DEBUG", url, c.autenticationLogger) | ||
|
||
var technicalError error | ||
var businessError error | ||
|
||
technicalError = backoff.Retry(func() error { | ||
_, technicalError, businessError, _ = c.CallSecretSafeAPI(url, "POST", bytes.Buffer{}, "SignOut", "") | ||
return technicalError | ||
}, c.ExponentialBackOff) | ||
|
||
if businessError != nil { | ||
utils.Logging("ERROR", businessError.Error(), c.autenticationLogger) | ||
return businessError | ||
} | ||
|
||
return nil | ||
} | ||
|
||
// CallSecretSafeAPI prepares http call | ||
func (c *AuthenticationObj) CallSecretSafeAPI(url string, httpMethod string, body bytes.Buffer, method string, accesToken string) (io.ReadCloser, error, error, int) { | ||
response, technicalError, businessError, scode := c.HttpRequest(url, httpMethod, body, accesToken) | ||
if technicalError != nil { | ||
messageLog := fmt.Sprintf("Error in %v %v \n", method, technicalError) | ||
utils.Logging("ERROR", messageLog, c.autenticationLogger) | ||
} | ||
|
||
if businessError != nil { | ||
messageLog := fmt.Sprintf("Error in %v: %v \n", method, businessError) | ||
utils.Logging("ERROR", messageLog, c.autenticationLogger) | ||
} | ||
return response, technicalError, businessError, scode | ||
} | ||
|
||
// HttpRequest makes http request to he server | ||
func (c *AuthenticationObj) HttpRequest(url string, method string, body bytes.Buffer, accesToken string) (closer io.ReadCloser, technicalError error, businessError error, scode int) { | ||
|
||
req, err := http.NewRequest(method, url, &body) | ||
if err != nil { | ||
return nil, err, nil, 0 | ||
} | ||
req.Header = http.Header{ | ||
"Content-Type": {"application/json"}, | ||
} | ||
|
||
if accesToken != "" { | ||
req.Header.Set("Authorization", "Bearer "+accesToken) | ||
} | ||
|
||
resp, err := c.httpClient.Do(req) | ||
if err != nil { | ||
utils.Logging("ERROR", err.Error(), c.autenticationLogger) | ||
return nil, err, nil, 0 | ||
} | ||
|
||
if resp.StatusCode >= http.StatusInternalServerError || resp.StatusCode == http.StatusRequestTimeout { | ||
err = fmt.Errorf("Error %v: StatusCode: %v, %v, %v", method, scode, err, body) | ||
utils.Logging("ERROR", err.Error(), c.autenticationLogger) | ||
return nil, err, nil, resp.StatusCode | ||
} | ||
|
||
if resp.StatusCode != http.StatusCreated && resp.StatusCode != http.StatusOK && resp.StatusCode != http.StatusNoContent { | ||
respBody := new(bytes.Buffer) | ||
respBody.ReadFrom(resp.Body) | ||
err = fmt.Errorf("got a non 200 status code: %v - %v", resp.StatusCode, respBody) | ||
return nil, nil, err, resp.StatusCode | ||
} | ||
|
||
return resp.Body, nil, nil, resp.StatusCode | ||
} |
Oops, something went wrong.