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 13, 2024
1 parent 8ad9d5d commit c600323
Show file tree
Hide file tree
Showing 11 changed files with 1,067 additions and 0 deletions.
142 changes: 142 additions & 0 deletions api/authentication/authentication_test.go
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)
}
}
232 changes: 232 additions & 0 deletions api/authentication/authetication.go
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
}
Loading

0 comments on commit c600323

Please sign in to comment.