Skip to content

Commit

Permalink
feat: initial commit
Browse files Browse the repository at this point in the history
  • Loading branch information
gitahernandez committed Feb 22, 2024
1 parent 9a5fca1 commit a84d6e1
Show file tree
Hide file tree
Showing 13 changed files with 1,814 additions and 0 deletions.
184 changes: 184 additions & 0 deletions api/authentication/authentication_test.go
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)
}
}
182 changes: 182 additions & 0 deletions api/authentication/authetication.go
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
}
29 changes: 29 additions & 0 deletions api/entities/entities.go
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"`
}
Loading

0 comments on commit a84d6e1

Please sign in to comment.