-
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
9a5fca1
commit a84d6e1
Showing
13 changed files
with
1,814 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,184 @@ | ||
// 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/logging" | ||
"go-client-library-passwordsafe/api/utils" | ||
"reflect" | ||
|
||
"net/http" | ||
"net/http/httptest" | ||
"testing" | ||
|
||
"go.uber.org/zap" | ||
) | ||
|
||
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 | ||
} | ||
|
||
func TestSignOut(t *testing.T) { | ||
logger, _ := zap.NewDevelopment() | ||
|
||
// create a zap logger wrapper | ||
zapLogger := logging.NewZapLogger(logger) | ||
|
||
httpClientObj, _ := utils.GetHttpClient(5, false, "", "", zapLogger) | ||
|
||
var authenticate, _ = Authenticate(*httpClientObj, "https://fake.api.com:443/BeyondTrust/api/public/v3/", "fakeone_a654+9sdf7+8we4f", "fakeone_aasd156465sfdef", zapLogger, 300) | ||
testConfig := UserTestConfig{ | ||
name: "TestSignOut", | ||
server: httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { | ||
_, err := w.Write([]byte(``)) | ||
if err != nil { | ||
t.Error("Test case Failed") | ||
} | ||
|
||
})), | ||
response: nil, | ||
} | ||
|
||
err := authenticate.SignOut(testConfig.server.URL) | ||
if err != nil { | ||
t.Errorf("Test case Failed: %v", err) | ||
} | ||
} | ||
|
||
func TestSignAppin(t *testing.T) { | ||
logger, _ := zap.NewDevelopment() | ||
|
||
// create a zap logger wrapper | ||
zapLogger := logging.NewZapLogger(logger) | ||
|
||
httpClientObj, _ := utils.GetHttpClient(5, false, "", "", zapLogger) | ||
|
||
var authenticate, _ = Authenticate(*httpClientObj, "https://fake.api.com:443/BeyondTrust/api/public/v3/", "fakeone_a654+9sdf7+8we4f", "fakeone_aasd156465sfdef", zapLogger, 300) | ||
testConfig := UserTestConfig{ | ||
name: "TestSignAppin", | ||
server: httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) { | ||
_, err := w.Write([]byte(`{"UserId":1, "EmailAddress":"Felipe"}`)) | ||
if err != nil { | ||
t.Error("Test case Failed") | ||
} | ||
})), | ||
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) { | ||
logger, _ := zap.NewDevelopment() | ||
|
||
// create a zap logger wrapper | ||
zapLogger := logging.NewZapLogger(logger) | ||
|
||
httpClientObj, _ := utils.GetHttpClient(5, false, "", "", zapLogger) | ||
|
||
var authenticate, _ = Authenticate(*httpClientObj, "https://fake.api.com:443/BeyondTrust/api/public/v3/", "fakeone_a654+9sdf7+8we4f", "fakeone_aasd156465sfdef", zapLogger, 300) | ||
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": | ||
_, err := w.Write([]byte(`{"access_token": "fake_token", "expires_in": 600, "token_type": "Bearer", "scope": "publicapi"}`)) | ||
if err != nil { | ||
t.Error("Test case Failed") | ||
} | ||
|
||
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) { | ||
logger, _ := zap.NewDevelopment() | ||
|
||
// create a zap logger wrapper | ||
zapLogger := logging.NewZapLogger(logger) | ||
|
||
httpClientObj, _ := utils.GetHttpClient(5, false, "", "", zapLogger) | ||
|
||
var authenticate, _ = Authenticate(*httpClientObj, "https://fake.api.com:443/BeyondTrust/api/public/v3/", "fakeone_a654+9sdf7+8we4f", "fakeone_aasd156465sfdef", zapLogger, 300) | ||
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": | ||
_, err := w.Write([]byte(`{"access_token": "fake_token", "expires_in": 600, "token_type": "Bearer", "scope": "publicapi"}`)) | ||
if err != nil { | ||
t.Error("Test case Failed") | ||
} | ||
|
||
case "/Auth/SignAppIn": | ||
_, err := w.Write([]byte(`{"UserId":1, "EmailAddress":"Felipe"}`)) | ||
|
||
if err != nil { | ||
t.Error("Test case Failed") | ||
} | ||
|
||
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,182 @@ | ||
// 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/logging" | ||
"go-client-library-passwordsafe/api/utils" | ||
"io" | ||
|
||
"net/url" | ||
"time" | ||
|
||
backoff "github.com/cenkalti/backoff/v4" | ||
) | ||
|
||
type AuthenticationObj struct { | ||
ApiUrl string | ||
clientId string | ||
clientSecret string | ||
HttpClient utils.HttpClientObj | ||
ExponentialBackOff *backoff.ExponentialBackOff | ||
log logging.Logger | ||
} | ||
|
||
// Authenticate in PS API | ||
func Authenticate(httpClient utils.HttpClientObj, endpointUrl string, clientId string, clientSecret string, logger logging.Logger, retryMaxElapsedTimeSeconds int) (*AuthenticationObj, error) { | ||
|
||
backoffDefinition := backoff.NewExponentialBackOff() | ||
backoffDefinition.InitialInterval = 1 * time.Second | ||
backoffDefinition.MaxElapsedTime = time.Duration(retryMaxElapsedTimeSeconds) * time.Second | ||
backoffDefinition.RandomizationFactor = 0.5 | ||
|
||
// Client | ||
var client = httpClient | ||
|
||
authenticationObj := &AuthenticationObj{ | ||
ApiUrl: endpointUrl, | ||
HttpClient: client, | ||
clientId: clientId, | ||
clientSecret: clientSecret, | ||
ExponentialBackOff: backoffDefinition, | ||
log: logger, | ||
} | ||
|
||
return authenticationObj, nil | ||
} | ||
|
||
// GetPasswordSafeAuthentication call get token and sign app endpoint | ||
func (authenticationObj *AuthenticationObj) GetPasswordSafeAuthentication() (entities.SignApinResponse, error) { | ||
accessToken, err := authenticationObj.GetToken(fmt.Sprintf("%v%v", authenticationObj.ApiUrl, "Auth/connect/token"), authenticationObj.clientId, authenticationObj.clientSecret) | ||
if err != nil { | ||
return entities.SignApinResponse{}, err | ||
} | ||
signApinResponse, err := authenticationObj.SignAppin(fmt.Sprintf("%v%v", authenticationObj.ApiUrl, "Auth/SignAppIn"), accessToken) | ||
if err != nil { | ||
return entities.SignApinResponse{}, err | ||
} | ||
return signApinResponse, nil | ||
} | ||
|
||
// GetToken get token from PS API | ||
func (authenticationObj *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, _ = authenticationObj.HttpClient.CallSecretSafeAPI(endpointUrl, "POST", buffer, "GetToken", "") | ||
return technicalError | ||
}, authenticationObj.ExponentialBackOff) | ||
|
||
if technicalError != nil { | ||
return "", technicalError | ||
} | ||
|
||
if businessError != nil { | ||
return "", businessError | ||
} | ||
|
||
defer body.Close() | ||
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 { | ||
authenticationObj.log.Error(err.Error()) | ||
return "", err | ||
} | ||
|
||
return data.AccessToken, nil | ||
|
||
} | ||
|
||
// SignAppin Signs app in PS API | ||
func (authenticationObj *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 = authenticationObj.HttpClient.CallSecretSafeAPI(endpointUrl, "POST", bytes.Buffer{}, "SignAppin", accessToken) | ||
if scode == 0 { | ||
return nil | ||
} | ||
return technicalError | ||
}, authenticationObj.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 { | ||
authenticationObj.log.Error(err.Error()) | ||
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 (authenticationObj *AuthenticationObj) SignOut(url string) error { | ||
authenticationObj.log.Debug(url) | ||
|
||
var technicalError error | ||
var businessError error | ||
var body io.ReadCloser | ||
|
||
technicalError = backoff.Retry(func() error { | ||
body, technicalError, businessError, _ = authenticationObj.HttpClient.CallSecretSafeAPI(url, "POST", bytes.Buffer{}, "SignOut", "") | ||
return technicalError | ||
}, authenticationObj.ExponentialBackOff) | ||
|
||
defer body.Close() | ||
if businessError != nil { | ||
authenticationObj.log.Error(businessError.Error()) | ||
return businessError | ||
} | ||
|
||
defer authenticationObj.HttpClient.HttpClient.CloseIdleConnections() | ||
|
||
return nil | ||
} |
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,29 @@ | ||
// Copyright 2024 BeyondTrust. All rights reserved. | ||
// Package entities implements DTO's used by Beyondtrust Secret Safe API. | ||
package entities | ||
|
||
type SignApinResponse struct { | ||
UserId int `json:"UserId"` | ||
EmailAddress string `json:"EmailAddress"` | ||
UserName string `json:"UserName"` | ||
Name string `json:"Name"` | ||
} | ||
|
||
type ManagedAccount struct { | ||
SystemId int | ||
AccountId int | ||
} | ||
|
||
type Secret struct { | ||
Id string | ||
Title string | ||
Password string | ||
SecretType string | ||
} | ||
|
||
type GetTokenResponse struct { | ||
AccessToken string `json:"access_token"` | ||
ExpiresIn int `json:"expires_in"` | ||
TokenType string `json:"token_type"` | ||
Scope string `json:"scope"` | ||
} |
Oops, something went wrong.