Compare commits
63 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| f14aec80d9 | |||
| cbd358ba85 | |||
| df845a7b4b | |||
| d8f6785032 | |||
| ff779d9456 | |||
| eba7784e59 | |||
| d022eb8ebd | |||
| 9c802b1239 | |||
| 2e3bd451df | |||
| 0608960a02 | |||
| bbbfb99dd9 | |||
| eef42d4b32 | |||
| d1a9414390 | |||
| dee3bb5d60 | |||
| dcbdef35cf | |||
| 1794fcd2ad | |||
| abfeb751b2 | |||
| 51f0aaf0ca | |||
| 8604a3568a | |||
| fee49889cf | |||
| 7e70411553 | |||
| 610edd6576 | |||
| 2f232e6850 | |||
| be4409a745 | |||
| 83b513ebf8 | |||
| 51e8d43795 | |||
| e816866e75 | |||
| ad1bf3cbfa | |||
| f4e3891f62 | |||
| cf73d6c399 | |||
| 74e81114de | |||
| 2299434912 | |||
| 3c6ab4bfde | |||
| a305db2206 | |||
| 6e8b78c6e2 | |||
| 4578694015 | |||
| dbc4412b9b | |||
| f151464330 | |||
| bcfa15e4a2 | |||
| 41c7e4a3ef | |||
| 5cdd3bd244 | |||
| 398d267ee5 | |||
| 26f8e762c7 | |||
| e395518de2 | |||
| 60795d413d | |||
| a2164c4beb | |||
| 90e028b173 | |||
| 6ce176b927 | |||
| 48a063eba2 | |||
| 5634c26039 | |||
| 429f45935a | |||
| 3c36225665 | |||
| f6417140cb | |||
| fb0273fe3c | |||
| eb147039b6 | |||
| c36ff4fa98 | |||
| 965d317a11 | |||
| 376278e2be | |||
| e0632f1895 | |||
| afc14717e3 | |||
| db43357ce0 | |||
| 1476170f88 | |||
| d1898339b1 |
+62
-28
@@ -1,41 +1,75 @@
|
|||||||
variables:
|
include:
|
||||||
GOCACHE: "${CI_PROJECT_DIR}/_go/cache"
|
- template: 'Workflows/MergeRequest-Pipelines.gitlab-ci.yml'
|
||||||
|
|
||||||
before_script:
|
image: golang:1.20.4
|
||||||
- mkdir -p ${CI_PROJECT_DIR}/_go/{pkg,bin,cache}
|
|
||||||
- rm -rf /go/pkg || true
|
|
||||||
- mkdir -p /go
|
|
||||||
- ln -s ${CI_PROJECT_DIR}/_go/pkg /go/pkg
|
|
||||||
- ln -s ${CI_PROJECT_DIR}/_go/bin /go/bin
|
|
||||||
|
|
||||||
cache:
|
|
||||||
key: "$CI_COMMIT_REF_NAME"
|
|
||||||
paths:
|
|
||||||
- _go
|
|
||||||
untracked: true
|
|
||||||
|
|
||||||
stages:
|
stages:
|
||||||
- deps
|
- deps
|
||||||
- test
|
- test
|
||||||
|
- prepare
|
||||||
|
- release
|
||||||
|
|
||||||
|
run-pre-commit:
|
||||||
|
stage: .pre
|
||||||
|
image: unbound/pre-commit
|
||||||
|
variables:
|
||||||
|
PRE_COMMIT_HOME: ${CI_PROJECT_DIR}/.cache/pre-commit
|
||||||
|
cache:
|
||||||
|
- key:
|
||||||
|
files:
|
||||||
|
- .pre-commit-config.yaml
|
||||||
|
paths:
|
||||||
|
- ${PRE_COMMIT_HOME}
|
||||||
|
script:
|
||||||
|
- pre-commit run --all-files
|
||||||
|
|
||||||
deps:
|
deps:
|
||||||
stage: deps
|
stage: deps
|
||||||
image: golang:1.13
|
|
||||||
script:
|
script:
|
||||||
- go get -mod=readonly
|
- go mod download
|
||||||
|
|
||||||
test:
|
test:
|
||||||
stage: test
|
stage: test
|
||||||
dependencies:
|
dependencies:
|
||||||
- deps
|
- deps
|
||||||
image: golang:1.13
|
|
||||||
script:
|
script:
|
||||||
- go fmt $(go list ./...)
|
- CGO_ENABLED=1 go test -mod=readonly -race -coverprofile=coverage.txt -covermode=atomic -coverpkg=$(go list ./... | tr '\n' , | sed 's/,$//') ./...
|
||||||
- go vet $(go list ./...)
|
- go tool cover -html=coverage.txt -o coverage.html
|
||||||
- unset "${!CI@}"
|
- go tool cover -func=coverage.txt
|
||||||
- CGO_ENABLED=1 go test -p 1 -mod=readonly -race -coverprofile=.testCoverage.txt -covermode=atomic -coverpkg=$(go list ./... | tr '\n' , | sed 's/,$//') ./...
|
- curl -Os https://uploader.codecov.io/latest/linux/codecov
|
||||||
- go tool cover -html=.testCoverage.txt -o coverage.html
|
- chmod +x codecov
|
||||||
- go tool cover -func=.testCoverage.txt
|
- ./codecov -t ${CODECOV_TOKEN} -R $CI_PROJECT_DIR -C $CI_COMMIT_SHA -r $CI_PROJECT_PATH
|
||||||
|
|
||||||
|
vulnerabilities:
|
||||||
|
stage: test
|
||||||
|
image: golang:1.20.4
|
||||||
|
script:
|
||||||
|
- go install golang.org/x/vuln/cmd/govulncheck@latest
|
||||||
|
- govulncheck ./...
|
||||||
|
|
||||||
|
prepare_release:
|
||||||
|
image: node:18
|
||||||
|
stage: prepare
|
||||||
|
before_script:
|
||||||
|
- npm install -g conventional-changelog-cli
|
||||||
|
script:
|
||||||
|
- echo "DESCRIPTION=$(conventional-changelog -p conventionalcommits)" > variables.env
|
||||||
artifacts:
|
artifacts:
|
||||||
paths:
|
reports:
|
||||||
- coverage.html
|
dotenv: variables.env
|
||||||
|
rules:
|
||||||
|
- if: $CI_COMMIT_TAG
|
||||||
|
|
||||||
|
release:
|
||||||
|
image: registry.gitlab.com/gitlab-org/release-cli:latest
|
||||||
|
stage: release
|
||||||
|
needs:
|
||||||
|
- job: prepare_release
|
||||||
|
artifacts: true
|
||||||
|
script:
|
||||||
|
- echo "Running release_job for $TAG"
|
||||||
|
release:
|
||||||
|
tag_name: '$CI_COMMIT_TAG'
|
||||||
|
description: '$DESCRIPTION'
|
||||||
|
rules:
|
||||||
|
- if: $CI_COMMIT_TAG
|
||||||
|
|||||||
@@ -0,0 +1,13 @@
|
|||||||
|
# To get started with Dependabot version updates, you'll need to specify which
|
||||||
|
# package ecosystems to update and where the package manifests are located.
|
||||||
|
# Please see the documentation for all configuration options:
|
||||||
|
# https://help.github.com/github/administering-a-repository/configuration-options-for-dependency-updates
|
||||||
|
|
||||||
|
version: 2
|
||||||
|
updates:
|
||||||
|
- package-ecosystem: "gomod"
|
||||||
|
directory: "/"
|
||||||
|
schedule:
|
||||||
|
interval: "daily"
|
||||||
|
open-pull-requests-limit: 20
|
||||||
|
rebase-strategy: none
|
||||||
@@ -0,0 +1,2 @@
|
|||||||
|
run:
|
||||||
|
allow-parallel-runners: true
|
||||||
@@ -0,0 +1,46 @@
|
|||||||
|
# See https://pre-commit.com for more information
|
||||||
|
# See https://pre-commit.com/hooks.html for more hooks
|
||||||
|
repos:
|
||||||
|
- repo: https://github.com/pre-commit/pre-commit-hooks
|
||||||
|
rev: v4.4.0
|
||||||
|
hooks:
|
||||||
|
- id: trailing-whitespace
|
||||||
|
- id: end-of-file-fixer
|
||||||
|
- id: check-yaml
|
||||||
|
args:
|
||||||
|
- --allow-multiple-documents
|
||||||
|
- id: check-added-large-files
|
||||||
|
- repo: https://github.com/jumanjihouse/pre-commit-hooks
|
||||||
|
rev: 3.0.0
|
||||||
|
hooks:
|
||||||
|
- id: markdownlint
|
||||||
|
- repo: https://gitlab.com/devopshq/gitlab-ci-linter
|
||||||
|
rev: v1.0.3
|
||||||
|
hooks:
|
||||||
|
- id: gitlab-ci-linter
|
||||||
|
args:
|
||||||
|
- --project
|
||||||
|
- unboundsoftware/shiny/authz_client
|
||||||
|
- repo: https://github.com/alessandrojcm/commitlint-pre-commit-hook
|
||||||
|
rev: v9.5.0
|
||||||
|
hooks:
|
||||||
|
- id: commitlint
|
||||||
|
stages: [ commit-msg ]
|
||||||
|
additional_dependencies: [ '@commitlint/config-conventional' ]
|
||||||
|
- repo: https://github.com/dnephin/pre-commit-golang
|
||||||
|
rev: v0.5.1
|
||||||
|
hooks:
|
||||||
|
- id: go-mod-tidy
|
||||||
|
- id: go-imports
|
||||||
|
args:
|
||||||
|
- -local
|
||||||
|
- gitlab.com/unboundsoftware/shiny/authz_client
|
||||||
|
- repo: https://github.com/lietu/go-pre-commit
|
||||||
|
rev: v0.0.1
|
||||||
|
hooks:
|
||||||
|
- id: go-test
|
||||||
|
- id: gofumpt
|
||||||
|
- repo: https://github.com/golangci/golangci-lint
|
||||||
|
rev: v1.53.2
|
||||||
|
hooks:
|
||||||
|
- id: golangci-lint
|
||||||
@@ -1,3 +1,4 @@
|
|||||||
# Shiny authz-client
|
# Shiny authz-client
|
||||||
|
|
||||||
[](https://gitlab.com/unboundsoftware/shiny/authz-client/commits/master)[](https://gitlab.com/unboundsoftware/shiny/authz-client/commits/master)
|
[](https://gitlab.com/unboundsoftware/shiny/authz_client/commits/main)
|
||||||
|
[](https://codecov.io/gl/unboundsoftware:shiny/authz_client)
|
||||||
|
|||||||
@@ -3,8 +3,11 @@ package client
|
|||||||
import (
|
import (
|
||||||
"encoding/json"
|
"encoding/json"
|
||||||
"fmt"
|
"fmt"
|
||||||
"io/ioutil"
|
"io"
|
||||||
"net/http"
|
"net/http"
|
||||||
|
"reflect"
|
||||||
|
|
||||||
|
"github.com/sparetimecoders/goamqp"
|
||||||
)
|
)
|
||||||
|
|
||||||
// CompanyPrivileges contains the privileges for a combination of email address and company id
|
// CompanyPrivileges contains the privileges for a combination of email address and company id
|
||||||
@@ -18,24 +21,11 @@ type CompanyPrivileges struct {
|
|||||||
Supplier bool `json:"supplier"`
|
Supplier bool `json:"supplier"`
|
||||||
}
|
}
|
||||||
|
|
||||||
// PrivilegeAdded is the event sent when a new privilege is added
|
|
||||||
type PrivilegeAdded struct {
|
|
||||||
Email string `json:"email"`
|
|
||||||
CompanyID string `json:"companyId"`
|
|
||||||
Admin bool `json:"admin"`
|
|
||||||
Company bool `json:"company"`
|
|
||||||
Consumer bool `json:"consumer"`
|
|
||||||
Time bool `json:"time"`
|
|
||||||
Invoicing bool `json:"invoicing"`
|
|
||||||
Accounting bool `json:"accounting"`
|
|
||||||
Supplier bool `json:"supplier"`
|
|
||||||
}
|
|
||||||
|
|
||||||
// PrivilegeHandler processes PrivilegeAdded-events and fetches the initial set of privileges from an authz-service
|
// PrivilegeHandler processes PrivilegeAdded-events and fetches the initial set of privileges from an authz-service
|
||||||
type PrivilegeHandler struct {
|
type PrivilegeHandler struct {
|
||||||
client *http.Client
|
client *http.Client
|
||||||
baseURL string
|
baseURL string
|
||||||
privileges map[string]map[string]CompanyPrivileges
|
privileges map[string]map[string]*CompanyPrivileges
|
||||||
}
|
}
|
||||||
|
|
||||||
// OptsFunc is used to configure the PrivilegeHandler
|
// OptsFunc is used to configure the PrivilegeHandler
|
||||||
@@ -53,7 +43,7 @@ func New(opts ...OptsFunc) *PrivilegeHandler {
|
|||||||
handler := &PrivilegeHandler{
|
handler := &PrivilegeHandler{
|
||||||
client: &http.Client{},
|
client: &http.Client{},
|
||||||
baseURL: "http://authz-service",
|
baseURL: "http://authz-service",
|
||||||
privileges: map[string]map[string]CompanyPrivileges{},
|
privileges: map[string]map[string]*CompanyPrivileges{},
|
||||||
}
|
}
|
||||||
for _, opt := range opts {
|
for _, opt := range opts {
|
||||||
opt(handler)
|
opt(handler)
|
||||||
@@ -68,7 +58,7 @@ func (h *PrivilegeHandler) Fetch() error {
|
|||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
|
|
||||||
buff, err := ioutil.ReadAll(resp.Body)
|
buff, err := io.ReadAll(resp.Body)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -81,30 +71,60 @@ func (h *PrivilegeHandler) Fetch() error {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Process privilege-related events and update the internal state
|
// Process privilege-related events and update the internal state
|
||||||
func (h *PrivilegeHandler) Process(msg interface{}) bool {
|
func (h *PrivilegeHandler) Process(msg interface{}, _ goamqp.Headers) (interface{}, error) {
|
||||||
if ev, ok := msg.(*PrivilegeAdded); ok {
|
switch ev := msg.(type) {
|
||||||
h.setPrivileges(ev)
|
case *UserAdded:
|
||||||
return true
|
if priv, exists := h.privileges[ev.Email]; exists {
|
||||||
|
priv[ev.CompanyID] = &CompanyPrivileges{}
|
||||||
|
} else {
|
||||||
|
h.privileges[ev.Email] = map[string]*CompanyPrivileges{
|
||||||
|
ev.CompanyID: {},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return nil, nil
|
||||||
|
case *UserRemoved:
|
||||||
|
if priv, exists := h.privileges[ev.Email]; exists {
|
||||||
|
delete(priv, ev.CompanyID)
|
||||||
|
}
|
||||||
|
return nil, nil
|
||||||
|
case *PrivilegeAdded:
|
||||||
|
h.setPrivileges(ev.Email, ev.CompanyID, ev.Privilege, true)
|
||||||
|
return nil, nil
|
||||||
|
case *PrivilegeRemoved:
|
||||||
|
h.setPrivileges(ev.Email, ev.CompanyID, ev.Privilege, false)
|
||||||
|
return nil, nil
|
||||||
|
default:
|
||||||
|
fmt.Printf("Got unexpected message type (%s): '%+v'\n", reflect.TypeOf(msg).String(), msg)
|
||||||
|
return nil, fmt.Errorf("unexpected event type: '%s'", reflect.TypeOf(msg))
|
||||||
}
|
}
|
||||||
return false
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func (h *PrivilegeHandler) setPrivileges(ev *PrivilegeAdded) {
|
func (h *PrivilegeHandler) setPrivileges(email, companyId string, privilege Privilege, set bool) {
|
||||||
if priv, exists := h.privileges[ev.Email]; exists {
|
if priv, exists := h.privileges[email]; exists {
|
||||||
priv[ev.CompanyID] = CompanyPrivileges{
|
if c, exists := priv[companyId]; exists {
|
||||||
Admin: ev.Admin,
|
switch privilege {
|
||||||
Company: ev.Company,
|
case PrivilegeAdmin:
|
||||||
Consumer: ev.Consumer,
|
c.Admin = set
|
||||||
Time: ev.Time,
|
case PrivilegeCompany:
|
||||||
Invoicing: ev.Invoicing,
|
c.Company = set
|
||||||
Accounting: ev.Accounting,
|
case PrivilegeConsumer:
|
||||||
Supplier: ev.Supplier,
|
c.Consumer = set
|
||||||
|
case PrivilegeTime:
|
||||||
|
c.Time = set
|
||||||
|
case PrivilegeInvoicing:
|
||||||
|
c.Invoicing = set
|
||||||
|
case PrivilegeAccounting:
|
||||||
|
c.Accounting = set
|
||||||
|
case PrivilegeSupplier:
|
||||||
|
c.Supplier = set
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
priv[companyId] = &CompanyPrivileges{}
|
||||||
|
h.setPrivileges(email, companyId, privilege, set)
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
h.privileges[ev.Email] = map[string]CompanyPrivileges{
|
h.privileges[email] = map[string]*CompanyPrivileges{}
|
||||||
ev.CompanyID: {},
|
h.setPrivileges(email, companyId, privilege, set)
|
||||||
}
|
|
||||||
h.setPrivileges(ev)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -113,10 +133,21 @@ func (h *PrivilegeHandler) CompaniesByUser(email string, predicate func(privileg
|
|||||||
var result []string
|
var result []string
|
||||||
if p, exists := h.privileges[email]; exists {
|
if p, exists := h.privileges[email]; exists {
|
||||||
for k, v := range p {
|
for k, v := range p {
|
||||||
if predicate(v) {
|
if predicate(*v) {
|
||||||
result = append(result, k)
|
result = append(result, k)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return result
|
return result
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// IsAllowed return true if the provided predicate return true for the privileges matching the provided email and companyID, return false otherwise
|
||||||
|
func (h *PrivilegeHandler) IsAllowed(email, companyID string, predicate func(privileges CompanyPrivileges) bool) bool {
|
||||||
|
if p, exists := h.privileges[email]; exists {
|
||||||
|
if v, exists := p[companyID]; exists {
|
||||||
|
return predicate(*v)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|||||||
+185
-42
@@ -2,18 +2,97 @@ package client
|
|||||||
|
|
||||||
import (
|
import (
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/stretchr/testify/assert"
|
|
||||||
"net/http"
|
"net/http"
|
||||||
"net/http/httptest"
|
"net/http/httptest"
|
||||||
|
"sort"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
|
"github.com/sparetimecoders/goamqp"
|
||||||
|
"github.com/stretchr/testify/assert"
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestPrivilegeHandler_Process_InvalidType(t *testing.T) {
|
func TestPrivilegeHandler_Process_InvalidType(t *testing.T) {
|
||||||
handler := New(WithBaseURL("base"))
|
handler := New(WithBaseURL("base"))
|
||||||
|
|
||||||
result := handler.Process("abc")
|
result, err := handler.Process("abc", goamqp.Headers{})
|
||||||
|
|
||||||
assert.False(t, result)
|
assert.Nil(t, result)
|
||||||
|
assert.EqualError(t, err, "unexpected event type: 'string'")
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPrivilegeHandler_Process_PrivilegeRemoved(t *testing.T) {
|
||||||
|
handler := New(WithBaseURL("base"))
|
||||||
|
|
||||||
|
result, err := handler.Process(&PrivilegeAdded{
|
||||||
|
Email: "jim@example.org",
|
||||||
|
CompanyID: "abc-123",
|
||||||
|
Privilege: PrivilegeAdmin,
|
||||||
|
}, goamqp.Headers{})
|
||||||
|
assert.Nil(t, result)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
companies := handler.CompaniesByUser("jim@example.org", func(privileges CompanyPrivileges) bool {
|
||||||
|
return privileges.Admin
|
||||||
|
})
|
||||||
|
|
||||||
|
assert.Equal(t, []string{"abc-123"}, companies)
|
||||||
|
|
||||||
|
result, err = handler.Process(&PrivilegeRemoved{
|
||||||
|
Email: "jim@example.org",
|
||||||
|
CompanyID: "abc-123",
|
||||||
|
Privilege: PrivilegeAdmin,
|
||||||
|
}, goamqp.Headers{})
|
||||||
|
assert.Nil(t, result)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
companies = handler.CompaniesByUser("jim@example.org", func(privileges CompanyPrivileges) bool {
|
||||||
|
return privileges.Admin
|
||||||
|
})
|
||||||
|
|
||||||
|
assert.Empty(t, companies)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPrivilegeHandler_Process_UserAdded_And_UserRemoved(t *testing.T) {
|
||||||
|
handler := New(WithBaseURL("base"))
|
||||||
|
|
||||||
|
result, err := handler.Process(&UserAdded{
|
||||||
|
Email: "jim@example.org",
|
||||||
|
CompanyID: "abc-123",
|
||||||
|
}, goamqp.Headers{})
|
||||||
|
assert.Nil(t, result)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
result, err = handler.Process(&UserAdded{
|
||||||
|
Email: "jim@example.org",
|
||||||
|
CompanyID: "abc-456",
|
||||||
|
}, goamqp.Headers{})
|
||||||
|
assert.Nil(t, result)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
companies := handler.CompaniesByUser("jim@example.org", func(privileges CompanyPrivileges) bool {
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
sort.Strings(companies)
|
||||||
|
assert.Equal(t, []string{"abc-123", "abc-456"}, companies)
|
||||||
|
|
||||||
|
result, err = handler.Process(&UserRemoved{
|
||||||
|
Email: "jim@example.org",
|
||||||
|
CompanyID: "abc-123",
|
||||||
|
}, goamqp.Headers{})
|
||||||
|
assert.Nil(t, result)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
result, err = handler.Process(&UserRemoved{
|
||||||
|
Email: "jim@example.org",
|
||||||
|
CompanyID: "abc-456",
|
||||||
|
}, goamqp.Headers{})
|
||||||
|
assert.Nil(t, result)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
companies = handler.CompaniesByUser("jim@example.org", func(privileges CompanyPrivileges) bool {
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
assert.Empty(t, companies)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestPrivilegeHandler_GetCompanies_Email_Not_Found(t *testing.T) {
|
func TestPrivilegeHandler_GetCompanies_Email_Not_Found(t *testing.T) {
|
||||||
@@ -29,41 +108,48 @@ func TestPrivilegeHandler_GetCompanies_Email_Not_Found(t *testing.T) {
|
|||||||
func TestPrivilegeHandler_GetCompanies_No_Companies_Found(t *testing.T) {
|
func TestPrivilegeHandler_GetCompanies_No_Companies_Found(t *testing.T) {
|
||||||
handler := New(WithBaseURL("base"))
|
handler := New(WithBaseURL("base"))
|
||||||
|
|
||||||
result := handler.Process(&PrivilegeAdded{
|
result, err := handler.Process(&UserAdded{
|
||||||
Email: "jim@example.org",
|
Email: "jim@example.org",
|
||||||
CompanyID: "abc-123",
|
CompanyID: "abc-123",
|
||||||
Admin: false,
|
}, goamqp.Headers{})
|
||||||
Company: false,
|
assert.Nil(t, result)
|
||||||
Consumer: false,
|
assert.NoError(t, err)
|
||||||
Time: false,
|
|
||||||
Invoicing: false,
|
|
||||||
Accounting: false,
|
|
||||||
Supplier: false,
|
|
||||||
})
|
|
||||||
assert.True(t, result)
|
|
||||||
|
|
||||||
companies := handler.CompaniesByUser("jim@example.org", func(privileges CompanyPrivileges) bool {
|
companies := handler.CompaniesByUser("jim@example.org", func(privileges CompanyPrivileges) bool {
|
||||||
return privileges.Admin
|
return privileges.Admin
|
||||||
})
|
})
|
||||||
|
|
||||||
assert.Empty(t, companies)
|
assert.Empty(t, companies)
|
||||||
|
|
||||||
|
companies = handler.CompaniesByUser("jim@example.org", func(privileges CompanyPrivileges) bool {
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
|
||||||
|
assert.Equal(t, []string{"abc-123"}, companies)
|
||||||
|
|
||||||
|
result, err = handler.Process(&UserRemoved{
|
||||||
|
Email: "jim@example.org",
|
||||||
|
CompanyID: "abc-123",
|
||||||
|
}, goamqp.Headers{})
|
||||||
|
assert.Nil(t, result)
|
||||||
|
assert.NoError(t, err)
|
||||||
|
|
||||||
|
companies = handler.CompaniesByUser("jim@example.org", func(privileges CompanyPrivileges) bool {
|
||||||
|
return true
|
||||||
|
})
|
||||||
|
assert.Empty(t, companies)
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestPrivilegeHandler_GetCompanies_Company_With_Company_Access_Found(t *testing.T) {
|
func TestPrivilegeHandler_GetCompanies_Company_With_Company_Access_Found(t *testing.T) {
|
||||||
handler := New(WithBaseURL("base"))
|
handler := New(WithBaseURL("base"))
|
||||||
|
|
||||||
result := handler.Process(&PrivilegeAdded{
|
result, err := handler.Process(&PrivilegeAdded{
|
||||||
Email: "jim@example.org",
|
Email: "jim@example.org",
|
||||||
CompanyID: "abc-123",
|
CompanyID: "abc-123",
|
||||||
Admin: false,
|
Privilege: PrivilegeCompany,
|
||||||
Company: true,
|
}, goamqp.Headers{})
|
||||||
Consumer: false,
|
assert.Nil(t, result)
|
||||||
Time: false,
|
assert.NoError(t, err)
|
||||||
Invoicing: false,
|
|
||||||
Accounting: false,
|
|
||||||
Supplier: false,
|
|
||||||
})
|
|
||||||
assert.True(t, result)
|
|
||||||
|
|
||||||
companies := handler.CompaniesByUser("jim@example.org", func(privileges CompanyPrivileges) bool {
|
companies := handler.CompaniesByUser("jim@example.org", func(privileges CompanyPrivileges) bool {
|
||||||
return privileges.Company
|
return privileges.Company
|
||||||
@@ -75,26 +161,83 @@ func TestPrivilegeHandler_GetCompanies_Company_With_Company_Access_Found(t *test
|
|||||||
func TestPrivilegeHandler_GetCompanies_Company_With_Admin_Access_Found(t *testing.T) {
|
func TestPrivilegeHandler_GetCompanies_Company_With_Admin_Access_Found(t *testing.T) {
|
||||||
handler := New(WithBaseURL("base"))
|
handler := New(WithBaseURL("base"))
|
||||||
|
|
||||||
result := handler.Process(&PrivilegeAdded{
|
result, err := handler.Process(&PrivilegeAdded{
|
||||||
Email: "jim@example.org",
|
Email: "jim@example.org",
|
||||||
CompanyID: "abc-123",
|
CompanyID: "abc-123",
|
||||||
Admin: true,
|
Privilege: PrivilegeConsumer,
|
||||||
Company: false,
|
}, goamqp.Headers{})
|
||||||
Consumer: false,
|
assert.Nil(t, result)
|
||||||
Time: false,
|
assert.NoError(t, err)
|
||||||
Invoicing: false,
|
|
||||||
Accounting: false,
|
|
||||||
Supplier: false,
|
|
||||||
})
|
|
||||||
assert.True(t, result)
|
|
||||||
|
|
||||||
companies := handler.CompaniesByUser("jim@example.org", func(privileges CompanyPrivileges) bool {
|
companies := handler.CompaniesByUser("jim@example.org", func(privileges CompanyPrivileges) bool {
|
||||||
return privileges.Admin
|
return privileges.Consumer
|
||||||
})
|
})
|
||||||
|
|
||||||
assert.Equal(t, []string{"abc-123"}, companies)
|
assert.Equal(t, []string{"abc-123"}, companies)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestPrivilegeHandler_IsAllowed_Return_False_If_No_Privileges(t *testing.T) {
|
||||||
|
handler := New(WithBaseURL("base"))
|
||||||
|
|
||||||
|
result := handler.IsAllowed("jim@example.org", "abc-123", func(privileges CompanyPrivileges) bool {
|
||||||
|
return privileges.Company
|
||||||
|
})
|
||||||
|
|
||||||
|
assert.False(t, result)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPrivilegeHandler_IsAllowed_Return_True_If_Privilege_Exists(t *testing.T) {
|
||||||
|
handler := New(WithBaseURL("base"))
|
||||||
|
|
||||||
|
_, _ = handler.Process(&PrivilegeAdded{
|
||||||
|
Email: "jim@example.org",
|
||||||
|
CompanyID: "abc-123",
|
||||||
|
Privilege: PrivilegeTime,
|
||||||
|
}, goamqp.Headers{})
|
||||||
|
|
||||||
|
result := handler.IsAllowed("jim@example.org", "abc-123", func(privileges CompanyPrivileges) bool {
|
||||||
|
return privileges.Time
|
||||||
|
})
|
||||||
|
|
||||||
|
assert.True(t, result)
|
||||||
|
|
||||||
|
_, _ = handler.Process(&PrivilegeAdded{
|
||||||
|
Email: "jim@example.org",
|
||||||
|
CompanyID: "abc-123",
|
||||||
|
Privilege: PrivilegeInvoicing,
|
||||||
|
}, goamqp.Headers{})
|
||||||
|
|
||||||
|
result = handler.IsAllowed("jim@example.org", "abc-123", func(privileges CompanyPrivileges) bool {
|
||||||
|
return privileges.Invoicing
|
||||||
|
})
|
||||||
|
|
||||||
|
assert.True(t, result)
|
||||||
|
|
||||||
|
_, _ = handler.Process(&PrivilegeAdded{
|
||||||
|
Email: "jim@example.org",
|
||||||
|
CompanyID: "abc-123",
|
||||||
|
Privilege: PrivilegeAccounting,
|
||||||
|
}, goamqp.Headers{})
|
||||||
|
|
||||||
|
result = handler.IsAllowed("jim@example.org", "abc-123", func(privileges CompanyPrivileges) bool {
|
||||||
|
return privileges.Accounting
|
||||||
|
})
|
||||||
|
|
||||||
|
assert.True(t, result)
|
||||||
|
|
||||||
|
_, _ = handler.Process(&PrivilegeAdded{
|
||||||
|
Email: "jim@example.org",
|
||||||
|
CompanyID: "abc-123",
|
||||||
|
Privilege: PrivilegeSupplier,
|
||||||
|
}, goamqp.Headers{})
|
||||||
|
|
||||||
|
result = handler.IsAllowed("jim@example.org", "abc-123", func(privileges CompanyPrivileges) bool {
|
||||||
|
return privileges.Supplier
|
||||||
|
})
|
||||||
|
|
||||||
|
assert.True(t, result)
|
||||||
|
}
|
||||||
|
|
||||||
func TestPrivilegeHandler_Fetch_Error_Response(t *testing.T) {
|
func TestPrivilegeHandler_Fetch_Error_Response(t *testing.T) {
|
||||||
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
w.WriteHeader(500)
|
w.WriteHeader(500)
|
||||||
@@ -106,7 +249,7 @@ func TestPrivilegeHandler_Fetch_Error_Response(t *testing.T) {
|
|||||||
server.Close()
|
server.Close()
|
||||||
|
|
||||||
err := handler.Fetch()
|
err := handler.Fetch()
|
||||||
assert.EqualError(t, err, fmt.Sprintf("Get http://%s/authz: dial tcp %s: connect: connection refused", baseURL, baseURL))
|
assert.EqualError(t, err, fmt.Sprintf("Get \"http://%s/authz\": dial tcp %s: connect: connection refused", baseURL, baseURL))
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestPrivilegeHandler_Fetch_Error_Unreadable_Body(t *testing.T) {
|
func TestPrivilegeHandler_Fetch_Error_Unreadable_Body(t *testing.T) {
|
||||||
@@ -160,7 +303,7 @@ func TestPrivilegeHandler_Fetch_Valid(t *testing.T) {
|
|||||||
|
|
||||||
err := handler.Fetch()
|
err := handler.Fetch()
|
||||||
assert.NoError(t, err)
|
assert.NoError(t, err)
|
||||||
expectedPrivileges := map[string]map[string]CompanyPrivileges{
|
expectedPrivileges := map[string]map[string]*CompanyPrivileges{
|
||||||
"jim@example.org": {
|
"jim@example.org": {
|
||||||
"00010203-0405-4607-8809-0a0b0c0d0e0f": {
|
"00010203-0405-4607-8809-0a0b0c0d0e0f": {
|
||||||
Admin: false,
|
Admin: false,
|
||||||
|
|||||||
@@ -0,0 +1,62 @@
|
|||||||
|
package client
|
||||||
|
|
||||||
|
// UserAdded is the event sent when a new user is added to a company
|
||||||
|
type UserAdded struct {
|
||||||
|
Email string `json:"email"`
|
||||||
|
CompanyID string `json:"companyId"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// UserRemoved is the event sent when a user is removed from a company
|
||||||
|
type UserRemoved struct {
|
||||||
|
Email string `json:"email"`
|
||||||
|
CompanyID string `json:"companyId"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// Privilege is an enumeration of all available privileges
|
||||||
|
type Privilege string
|
||||||
|
|
||||||
|
const (
|
||||||
|
PrivilegeAdmin = "ADMIN"
|
||||||
|
PrivilegeCompany = "COMPANY"
|
||||||
|
PrivilegeConsumer = "CONSUMER"
|
||||||
|
PrivilegeTime = "TIME"
|
||||||
|
PrivilegeInvoicing = "INVOICING"
|
||||||
|
PrivilegeAccounting = "ACCOUNTING"
|
||||||
|
PrivilegeSupplier = "SUPPLIER"
|
||||||
|
)
|
||||||
|
|
||||||
|
var AllPrivilege = []Privilege{
|
||||||
|
PrivilegeAdmin,
|
||||||
|
PrivilegeCompany,
|
||||||
|
PrivilegeConsumer,
|
||||||
|
PrivilegeTime,
|
||||||
|
PrivilegeInvoicing,
|
||||||
|
PrivilegeAccounting,
|
||||||
|
PrivilegeSupplier,
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e Privilege) IsValid() bool {
|
||||||
|
switch e {
|
||||||
|
case PrivilegeAdmin, PrivilegeCompany, PrivilegeConsumer, PrivilegeTime, PrivilegeInvoicing, PrivilegeAccounting, PrivilegeSupplier:
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
func (e Privilege) String() string {
|
||||||
|
return string(e)
|
||||||
|
}
|
||||||
|
|
||||||
|
// PrivilegeAdded is the event sent when a new privilege is added
|
||||||
|
type PrivilegeAdded struct {
|
||||||
|
Email string `json:"email"`
|
||||||
|
CompanyID string `json:"companyId"`
|
||||||
|
Privilege Privilege `json:"privilege"`
|
||||||
|
}
|
||||||
|
|
||||||
|
// PrivilegeRemoved is the event sent when a privilege is removed
|
||||||
|
type PrivilegeRemoved struct {
|
||||||
|
Email string `json:"email"`
|
||||||
|
CompanyID string `json:"companyId"`
|
||||||
|
Privilege Privilege `json:"privilege"`
|
||||||
|
}
|
||||||
+110
@@ -0,0 +1,110 @@
|
|||||||
|
package client
|
||||||
|
|
||||||
|
import "testing"
|
||||||
|
|
||||||
|
func TestPrivilege_IsValid(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
e Privilege
|
||||||
|
want bool
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "Admin",
|
||||||
|
e: "ADMIN",
|
||||||
|
want: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Company",
|
||||||
|
e: "COMPANY",
|
||||||
|
want: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Consumer",
|
||||||
|
e: "CONSUMER",
|
||||||
|
want: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Time",
|
||||||
|
e: "TIME",
|
||||||
|
want: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Invoicing",
|
||||||
|
e: "INVOICING",
|
||||||
|
want: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Accounting",
|
||||||
|
e: "ACCOUNTING",
|
||||||
|
want: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Supplier",
|
||||||
|
e: "SUPPLIER",
|
||||||
|
want: true,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Invalid",
|
||||||
|
e: "BLUTTI",
|
||||||
|
want: false,
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
if got := tt.e.IsValid(); got != tt.want {
|
||||||
|
t.Errorf("IsValid() = %v, want %v", got, tt.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPrivilege_String(t *testing.T) {
|
||||||
|
tests := []struct {
|
||||||
|
name string
|
||||||
|
e Privilege
|
||||||
|
want string
|
||||||
|
}{
|
||||||
|
{
|
||||||
|
name: "Admin",
|
||||||
|
e: "ADMIN",
|
||||||
|
want: "ADMIN",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Company",
|
||||||
|
e: "COMPANY",
|
||||||
|
want: "COMPANY",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Consumer",
|
||||||
|
e: "CONSUMER",
|
||||||
|
want: "CONSUMER",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Time",
|
||||||
|
e: "TIME",
|
||||||
|
want: "TIME",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Invoicing",
|
||||||
|
e: "INVOICING",
|
||||||
|
want: "INVOICING",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Accounting",
|
||||||
|
e: "ACCOUNTING",
|
||||||
|
want: "ACCOUNTING",
|
||||||
|
},
|
||||||
|
{
|
||||||
|
name: "Supplier",
|
||||||
|
e: "SUPPLIER",
|
||||||
|
want: "SUPPLIER",
|
||||||
|
},
|
||||||
|
}
|
||||||
|
for _, tt := range tests {
|
||||||
|
t.Run(tt.name, func(t *testing.T) {
|
||||||
|
if got := tt.e.String(); got != tt.want {
|
||||||
|
t.Errorf("String() = %v, want %v", got, tt.want)
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -1,5 +1,17 @@
|
|||||||
module gitlab.com/unboundsoftware/shiny/authz_client
|
module gitlab.com/unboundsoftware/shiny/authz_client
|
||||||
|
|
||||||
go 1.13
|
go 1.19
|
||||||
|
|
||||||
require github.com/stretchr/testify v1.4.0
|
require (
|
||||||
|
github.com/sparetimecoders/goamqp v0.1.4
|
||||||
|
github.com/stretchr/testify v1.8.4
|
||||||
|
)
|
||||||
|
|
||||||
|
require (
|
||||||
|
github.com/davecgh/go-spew v1.1.1 // indirect
|
||||||
|
github.com/google/uuid v1.3.0 // indirect
|
||||||
|
github.com/pkg/errors v0.9.1 // indirect
|
||||||
|
github.com/pmezard/go-difflib v1.0.0 // indirect
|
||||||
|
github.com/rabbitmq/amqp091-go v1.8.1 // indirect
|
||||||
|
gopkg.in/yaml.v3 v3.0.1 // indirect
|
||||||
|
)
|
||||||
|
|||||||
@@ -1,11 +1,32 @@
|
|||||||
github.com/davecgh/go-spew v1.1.0 h1:ZDRjVQ15GmhC3fiQ8ni8+OwkZQO4DARzQgrnXU1Liz8=
|
|
||||||
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c=
|
||||||
|
github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38=
|
||||||
|
github.com/google/uuid v1.3.0 h1:t6JiXgmwXMjEs8VusXIJk2BXHsn+wx8BZdTaoZ5fu7I=
|
||||||
|
github.com/google/uuid v1.3.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo=
|
||||||
|
github.com/kr/pretty v0.1.0 h1:L/CwN0zerZDmRFUapSPitk6f+Q3+0za1rQkzVuMiMFI=
|
||||||
|
github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo=
|
||||||
|
github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ=
|
||||||
|
github.com/kr/text v0.1.0 h1:45sCR5RtlFHMR4UwH9sdQ5TC8v0qDQCHnXt+kaKSTVE=
|
||||||
|
github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI=
|
||||||
|
github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4=
|
||||||
|
github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0=
|
||||||
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM=
|
||||||
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4=
|
||||||
|
github.com/rabbitmq/amqp091-go v1.8.1 h1:RejT1SBUim5doqcL6s7iN6SBmsQqyTgXb1xMlH0h1hA=
|
||||||
|
github.com/rabbitmq/amqp091-go v1.8.1/go.mod h1:+jPrT9iY2eLjRaMSRHUhc3z14E/l85kv/f+6luSD3pc=
|
||||||
|
github.com/sparetimecoders/goamqp v0.1.4 h1:zNvnCJYb5vraMx+OJCCuPIaXP8ub3Et15ff8ylZrPkY=
|
||||||
|
github.com/sparetimecoders/goamqp v0.1.4/go.mod h1:WUJIWrbwl6rWxbfQTsy/doY7yHQL55L7M89k7ry6ouU=
|
||||||
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME=
|
||||||
github.com/stretchr/testify v1.4.0 h1:2E4SXV/wtOkTonXsotYi4li6zVWxYlZuYNCXe9XRJyk=
|
github.com/stretchr/objx v0.4.0/go.mod h1:YvHI0jy2hoMjB+UWwv71VJQ9isScKT/TqJzVSSt89Yw=
|
||||||
github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4=
|
github.com/stretchr/testify v1.7.1/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405 h1:yhCVgyC4o1eVCa2tZl7eS0r+SDo693bJlVdllGtEeKM=
|
github.com/stretchr/testify v1.8.0/go.mod h1:yNjHg4UonilssWZ8iaSj1OCr/vHnekPRkoO+kdMU+MU=
|
||||||
|
github.com/stretchr/testify v1.8.4 h1:CcVxjf3Q8PM0mHUKJCdn+eZZtm5yQwehR5yeSVQQcUk=
|
||||||
|
github.com/stretchr/testify v1.8.4/go.mod h1:sz/lmYIOXD/1dqDmKjjqLyZ2RngseejIcXlSw2iwfAo=
|
||||||
|
go.uber.org/goleak v1.2.1 h1:NBol2c7O1ZokfZ0LEU9K6Whx/KnwvepVetCUhtKja4A=
|
||||||
|
go.uber.org/goleak v1.2.1/go.mod h1:qlT2yGI9QafXHhZZLxlSuNsMw3FFLxBr+tBRlmO1xH4=
|
||||||
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
gopkg.in/check.v1 v0.0.0-20161208181325-20d25e280405/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
gopkg.in/yaml.v2 v2.2.2 h1:ZCJp+EgiOT7lHqUV2J862kp8Qj64Jo6az82+3Td9dZw=
|
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127 h1:qIbj1fsPNlZgppZ+VLlY7N33q108Sa+fhmuc+sWQYwY=
|
||||||
gopkg.in/yaml.v2 v2.2.2/go.mod h1:hI93XBmqTisBFMUTm0b8Fm+jr3Dg1NNxqwp+5A1VGuI=
|
gopkg.in/check.v1 v1.0.0-20180628173108-788fd7840127/go.mod h1:Co6ibVJAznAaIkqp8huTwlJQCZ016jof/cbN4VW5Yz0=
|
||||||
|
gopkg.in/yaml.v3 v3.0.0-20200313102051-9f266ea9e77c/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
gopkg.in/yaml.v3 v3.0.1 h1:fxVm/GzAzEWqLHuvctI91KS9hhNmmWOoWu0XTYJS7CA=
|
||||||
|
gopkg.in/yaml.v3 v3.0.1/go.mod h1:K4uyk7z7BCEPqu6E+C64Yfv1cQ7kz7rIZviUmN+EgEM=
|
||||||
|
|||||||
Reference in New Issue
Block a user