Compare commits
29 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 775d25cb59 | |||
| ef992cb9db | |||
| c3b8a3f1ce | |||
| 45512115c5 | |||
| fe0abd62c8 | |||
| a54cf45a4b | |||
| f9a5ef7085 | |||
| 200e7cf963 | |||
| 110f6206f9 | |||
| c53d80792c | |||
| ebc0c3bb8e | |||
| cb59762fc9 | |||
| a82466cb27 | |||
| 29eab978f7 | |||
| f3166426b6 | |||
|
3171c53393
|
|||
| 7af8e00b4c | |||
|
0c0f321b33
|
|||
| 87805f1552 | |||
| 74ee30bccc | |||
| 646e4f31c4 | |||
| 881fac379f | |||
| 2c2bd2798f | |||
| 204b108ece | |||
| fa795a58cf | |||
| 60650b9c04 | |||
|
60d9eea9c9
|
|||
| dd571f8d85 | |||
| 1f822b2957 |
@@ -17,6 +17,61 @@ jobs:
|
|||||||
- name: Run tests
|
- name: Run tests
|
||||||
run: go test -race -coverprofile=coverage.txt ./...
|
run: go test -race -coverprofile=coverage.txt ./...
|
||||||
|
|
||||||
|
- name: Check coverage
|
||||||
|
uses: vladopajic/go-test-coverage@v2
|
||||||
|
with:
|
||||||
|
config: ./.testcoverage.yml
|
||||||
|
|
||||||
|
# Download baseline coverage from main branch (for PRs)
|
||||||
|
- name: Download baseline coverage
|
||||||
|
if: gitea.event_name == 'pull_request'
|
||||||
|
uses: actions/download-artifact@v3
|
||||||
|
with:
|
||||||
|
name: coverage-baseline
|
||||||
|
path: ./baseline
|
||||||
|
continue-on-error: true
|
||||||
|
|
||||||
|
# Compare coverage against baseline (for PRs)
|
||||||
|
- name: Compare coverage
|
||||||
|
if: gitea.event_name == 'pull_request'
|
||||||
|
run: |
|
||||||
|
CURRENT=$(go tool cover -func=coverage.txt | grep "^total:" | awk '{print $NF}' | tr -d '%')
|
||||||
|
if [ -f ./baseline/coverage.txt ]; then
|
||||||
|
BASE=$(go tool cover -func=./baseline/coverage.txt | grep "^total:" | awk '{print $NF}' | tr -d '%')
|
||||||
|
echo "Base coverage: ${BASE}%"
|
||||||
|
echo "Current coverage: ${CURRENT}%"
|
||||||
|
if [ "$(echo "$CURRENT < $BASE" | bc -l)" -eq 1 ]; then
|
||||||
|
echo "::error::Coverage decreased from ${BASE}% to ${CURRENT}%"
|
||||||
|
exit 1
|
||||||
|
fi
|
||||||
|
echo "Coverage maintained or improved: ${BASE}% -> ${CURRENT}%"
|
||||||
|
else
|
||||||
|
echo "No baseline coverage found, skipping comparison"
|
||||||
|
echo "Current coverage: ${CURRENT}%"
|
||||||
|
fi
|
||||||
|
|
||||||
|
# Upload coverage as baseline (only on main)
|
||||||
|
- name: Upload coverage baseline
|
||||||
|
if: gitea.ref == 'refs/heads/main'
|
||||||
|
uses: actions/upload-artifact@v3
|
||||||
|
with:
|
||||||
|
name: coverage-baseline
|
||||||
|
path: coverage.txt
|
||||||
|
retention-days: 90
|
||||||
|
|
||||||
|
# Post coverage to PR comment
|
||||||
|
- name: Post coverage comment
|
||||||
|
if: gitea.event_name == 'pull_request'
|
||||||
|
env:
|
||||||
|
GITEA_TOKEN: ${{ secrets.GITEA_TOKEN }}
|
||||||
|
GITEA_URL: ${{ gitea.server_url }}
|
||||||
|
run: |
|
||||||
|
COVERAGE=$(go tool cover -func=coverage.txt | grep "^total:" | awk '{print $NF}')
|
||||||
|
curl -X POST "${GITEA_URL}/api/v1/repos/${{ gitea.repository }}/issues/${{ gitea.event.pull_request.number }}/comments" \
|
||||||
|
-H "Authorization: token ${GITEA_TOKEN}" \
|
||||||
|
-H "Content-Type: application/json" \
|
||||||
|
-d "{\"body\": \"## Coverage Report\n\nTotal coverage: **${COVERAGE}**\"}"
|
||||||
|
|
||||||
vulnerabilities:
|
vulnerabilities:
|
||||||
runs-on: ubuntu-latest
|
runs-on: ubuntu-latest
|
||||||
steps:
|
steps:
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ jobs:
|
|||||||
- uses: actions/setup-go@v6
|
- uses: actions/setup-go@v6
|
||||||
with:
|
with:
|
||||||
go-version: stable
|
go-version: stable
|
||||||
- uses: actions/setup-python@v5
|
- uses: actions/setup-python@v6
|
||||||
with:
|
with:
|
||||||
python-version: '3.14'
|
python-version: '3.14'
|
||||||
- name: Install goimports
|
- name: Install goimports
|
||||||
|
|||||||
@@ -1,38 +0,0 @@
|
|||||||
include:
|
|
||||||
- template: 'Workflows/MergeRequest-Pipelines.gitlab-ci.yml'
|
|
||||||
- project: unboundsoftware/ci-templates
|
|
||||||
file: Defaults.gitlab-ci.yml
|
|
||||||
- project: unboundsoftware/ci-templates
|
|
||||||
file: Release.gitlab-ci.yml
|
|
||||||
- project: unboundsoftware/ci-templates
|
|
||||||
file: Pre-Commit-Go.gitlab-ci.yml
|
|
||||||
|
|
||||||
image: amd64/golang:1.25.5@sha256:ad03ba93327b8a6143b49373790b5d92c28067bdb814418509466122ee9c9e63
|
|
||||||
|
|
||||||
stages:
|
|
||||||
- deps
|
|
||||||
- test
|
|
||||||
|
|
||||||
deps:
|
|
||||||
stage: deps
|
|
||||||
script:
|
|
||||||
- go mod download
|
|
||||||
|
|
||||||
test:
|
|
||||||
stage: test
|
|
||||||
dependencies:
|
|
||||||
- deps
|
|
||||||
script:
|
|
||||||
- CGO_ENABLED=1 go test -mod=readonly -race -coverprofile=coverage.txt -covermode=atomic -coverpkg=$(go list ./... | tr '\n' , | sed 's/,$//') ./...
|
|
||||||
- go tool cover -html=coverage.txt -o coverage.html
|
|
||||||
- go tool cover -func=coverage.txt
|
|
||||||
- curl -Os https://uploader.codecov.io/latest/linux/codecov
|
|
||||||
- chmod +x codecov
|
|
||||||
- ./codecov -t ${CODECOV_TOKEN} -R $CI_PROJECT_DIR -C $CI_COMMIT_SHA -r $CI_PROJECT_PATH
|
|
||||||
|
|
||||||
vulnerabilities:
|
|
||||||
stage: test
|
|
||||||
image: amd64/golang:1.25.5@sha256:ad03ba93327b8a6143b49373790b5d92c28067bdb814418509466122ee9c9e63
|
|
||||||
script:
|
|
||||||
- go install golang.org/x/vuln/cmd/govulncheck@latest
|
|
||||||
- govulncheck ./...
|
|
||||||
@@ -11,7 +11,7 @@ repos:
|
|||||||
- --allow-multiple-documents
|
- --allow-multiple-documents
|
||||||
- id: check-added-large-files
|
- id: check-added-large-files
|
||||||
- repo: https://github.com/alessandrojcm/commitlint-pre-commit-hook
|
- repo: https://github.com/alessandrojcm/commitlint-pre-commit-hook
|
||||||
rev: v9.23.0
|
rev: v9.24.0
|
||||||
hooks:
|
hooks:
|
||||||
- id: commitlint
|
- id: commitlint
|
||||||
stages: [ commit-msg ]
|
stages: [ commit-msg ]
|
||||||
@@ -23,17 +23,17 @@ repos:
|
|||||||
- id: go-imports
|
- id: go-imports
|
||||||
args:
|
args:
|
||||||
- -local
|
- -local
|
||||||
- git.unbound.se/shiny/authz_client
|
- gitea.unbound.se/shiny/authz_client
|
||||||
- repo: https://github.com/lietu/go-pre-commit
|
- repo: https://github.com/lietu/go-pre-commit
|
||||||
rev: v1.0.0
|
rev: v1.0.0
|
||||||
hooks:
|
hooks:
|
||||||
- id: go-test
|
- id: go-test
|
||||||
- id: gofumpt
|
- id: gofumpt
|
||||||
- repo: https://github.com/golangci/golangci-lint
|
- repo: https://github.com/golangci/golangci-lint
|
||||||
rev: v2.8.0
|
rev: v2.11.4
|
||||||
hooks:
|
hooks:
|
||||||
- id: golangci-lint-full
|
- id: golangci-lint-full
|
||||||
- repo: https://github.com/gitleaks/gitleaks
|
- repo: https://github.com/gitleaks/gitleaks
|
||||||
rev: v8.30.0
|
rev: v8.30.1
|
||||||
hooks:
|
hooks:
|
||||||
- id: gitleaks
|
- id: gitleaks
|
||||||
|
|||||||
@@ -0,0 +1,13 @@
|
|||||||
|
# Coverage configuration for go-test-coverage
|
||||||
|
# https://github.com/vladopajic/go-test-coverage
|
||||||
|
|
||||||
|
profile: coverage.txt
|
||||||
|
|
||||||
|
threshold:
|
||||||
|
file: 0
|
||||||
|
package: 0
|
||||||
|
total: 0
|
||||||
|
|
||||||
|
exclude:
|
||||||
|
paths:
|
||||||
|
- _test\.go$
|
||||||
@@ -2,6 +2,37 @@
|
|||||||
|
|
||||||
All notable changes to this project will be documented in this file.
|
All notable changes to this project will be documented in this file.
|
||||||
|
|
||||||
|
## [0.5.0] - 2026-03-12
|
||||||
|
|
||||||
|
### 🚀 Features
|
||||||
|
|
||||||
|
- *(client)* Add API key authentication for /authz endpoint (#294)
|
||||||
|
|
||||||
|
### ⚙️ Miscellaneous Tasks
|
||||||
|
|
||||||
|
- *(deps)* Update golang:1.25.5 docker digest to 3a01526 (#271)
|
||||||
|
- *(deps)* Update pre-commit hook alessandrojcm/commitlint-pre-commit-hook to v9.24.0 (#273)
|
||||||
|
- *(deps)* Update dependency go to v1.25.6 (#274)
|
||||||
|
- *(deps)* Update golang docker tag to v1.25.6 (#275)
|
||||||
|
- Remove GitLab CI configuration
|
||||||
|
- Add code coverage integration
|
||||||
|
- *(deps)* Update dependency go to v1.25.7 (#279)
|
||||||
|
- *(deps)* Update dependency go to v1.26.0 (#280)
|
||||||
|
- *(deps)* Update pre-commit hook golangci/golangci-lint to v2.9.0 (#281)
|
||||||
|
- *(deps)* Update pre-commit hook golangci/golangci-lint to v2.10.0 (#282)
|
||||||
|
- *(deps)* Update pre-commit hook golangci/golangci-lint to v2.10.1 (#283)
|
||||||
|
- *(deps)* Update dependency go to v1.26.1 (#286)
|
||||||
|
- *(deps)* Update pre-commit hook golangci/golangci-lint to v2.11.1 (#288)
|
||||||
|
- *(deps)* Update pre-commit hook golangci/golangci-lint to v2.11.2 (#290)
|
||||||
|
- *(deps)* Update pre-commit hook golangci/golangci-lint to v2.11.3 (#292)
|
||||||
|
|
||||||
|
## [0.4.1] - 2026-01-09
|
||||||
|
|
||||||
|
### ⚙️ Miscellaneous Tasks
|
||||||
|
|
||||||
|
- *(deps)* Update actions/setup-python action to v6
|
||||||
|
- Migrate module path to gitea.unbound.se
|
||||||
|
|
||||||
## [0.4.0] - 2026-01-09
|
## [0.4.0] - 2026-01-09
|
||||||
|
|
||||||
### 🚀 Features
|
### 🚀 Features
|
||||||
|
|||||||
@@ -17,7 +17,7 @@ Provides a client for the authz-service, handling privilege management for users
|
|||||||
### Usage
|
### Usage
|
||||||
|
|
||||||
```go
|
```go
|
||||||
import client "git.unbound.se/shiny/authz_client"
|
import client "gitea.unbound.se/shiny/authz_client"
|
||||||
|
|
||||||
// Create handler with options
|
// Create handler with options
|
||||||
handler := client.New(client.WithBaseURL("http://authz-service"))
|
handler := client.New(client.WithBaseURL("http://authz-service"))
|
||||||
|
|||||||
@@ -28,6 +28,7 @@ type PrivilegeHandler struct {
|
|||||||
*sync.RWMutex
|
*sync.RWMutex
|
||||||
client *http.Client
|
client *http.Client
|
||||||
baseURL string
|
baseURL string
|
||||||
|
apiKey string
|
||||||
privileges map[string]map[string]*CompanyPrivileges
|
privileges map[string]map[string]*CompanyPrivileges
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -41,6 +42,13 @@ func WithBaseURL(url string) OptsFunc {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// WithAPIKey sets an API key used as a Bearer token when fetching privileges
|
||||||
|
func WithAPIKey(key string) OptsFunc {
|
||||||
|
return func(handler *PrivilegeHandler) {
|
||||||
|
handler.apiKey = key
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// New creates a new PrivilegeHandler. Pass OptsFuncs to configure.
|
// New creates a new PrivilegeHandler. Pass OptsFuncs to configure.
|
||||||
func New(opts ...OptsFunc) *PrivilegeHandler {
|
func New(opts ...OptsFunc) *PrivilegeHandler {
|
||||||
handler := &PrivilegeHandler{
|
handler := &PrivilegeHandler{
|
||||||
@@ -57,7 +65,16 @@ func New(opts ...OptsFunc) *PrivilegeHandler {
|
|||||||
|
|
||||||
// Fetch the initial set of privileges from an authz-service
|
// Fetch the initial set of privileges from an authz-service
|
||||||
func (h *PrivilegeHandler) Fetch() error {
|
func (h *PrivilegeHandler) Fetch() error {
|
||||||
resp, err := h.client.Get(fmt.Sprintf("%s/authz", h.baseURL))
|
req, err := http.NewRequest(http.MethodGet, fmt.Sprintf("%s/authz", h.baseURL), nil)
|
||||||
|
if err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
|
||||||
|
if h.apiKey != "" {
|
||||||
|
req.Header.Set("Authorization", "Bearer "+h.apiKey)
|
||||||
|
}
|
||||||
|
|
||||||
|
resp, err := h.client.Do(req)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return err
|
return err
|
||||||
}
|
}
|
||||||
@@ -87,13 +104,14 @@ func (h *PrivilegeHandler) Setup() []goamqp.Setup {
|
|||||||
|
|
||||||
// Process privilege-related events and update the internal state
|
// Process privilege-related events and update the internal state
|
||||||
func (h *PrivilegeHandler) Process(msg interface{}, _ goamqp.Headers) (interface{}, error) {
|
func (h *PrivilegeHandler) Process(msg interface{}, _ goamqp.Headers) (interface{}, error) {
|
||||||
|
h.Lock()
|
||||||
|
defer h.Unlock()
|
||||||
|
|
||||||
switch ev := msg.(type) {
|
switch ev := msg.(type) {
|
||||||
case *UserAdded:
|
case *UserAdded:
|
||||||
if priv, exists := h.privileges[ev.Email]; exists {
|
if priv, exists := h.privileges[ev.Email]; exists {
|
||||||
priv[ev.CompanyID] = &CompanyPrivileges{}
|
priv[ev.CompanyID] = &CompanyPrivileges{}
|
||||||
} else {
|
} else {
|
||||||
h.Lock()
|
|
||||||
defer h.Unlock()
|
|
||||||
h.privileges[ev.Email] = map[string]*CompanyPrivileges{
|
h.privileges[ev.Email] = map[string]*CompanyPrivileges{
|
||||||
ev.CompanyID: {},
|
ev.CompanyID: {},
|
||||||
}
|
}
|
||||||
@@ -101,19 +119,13 @@ func (h *PrivilegeHandler) Process(msg interface{}, _ goamqp.Headers) (interface
|
|||||||
return nil, nil
|
return nil, nil
|
||||||
case *UserRemoved:
|
case *UserRemoved:
|
||||||
if priv, exists := h.privileges[ev.Email]; exists {
|
if priv, exists := h.privileges[ev.Email]; exists {
|
||||||
h.Lock()
|
|
||||||
defer h.Unlock()
|
|
||||||
delete(priv, ev.CompanyID)
|
delete(priv, ev.CompanyID)
|
||||||
}
|
}
|
||||||
return nil, nil
|
return nil, nil
|
||||||
case *PrivilegeAdded:
|
case *PrivilegeAdded:
|
||||||
h.Lock()
|
|
||||||
defer h.Unlock()
|
|
||||||
h.setPrivileges(ev.Email, ev.CompanyID, ev.Privilege, true)
|
h.setPrivileges(ev.Email, ev.CompanyID, ev.Privilege, true)
|
||||||
return nil, nil
|
return nil, nil
|
||||||
case *PrivilegeRemoved:
|
case *PrivilegeRemoved:
|
||||||
h.Lock()
|
|
||||||
defer h.Unlock()
|
|
||||||
h.setPrivileges(ev.Email, ev.CompanyID, ev.Privilege, false)
|
h.setPrivileges(ev.Email, ev.CompanyID, ev.Privilege, false)
|
||||||
return nil, nil
|
return nil, nil
|
||||||
default:
|
default:
|
||||||
|
|||||||
@@ -251,6 +251,39 @@ func TestPrivilegeHandler_IsAllowed_Return_True_If_Privilege_Exists(t *testing.T
|
|||||||
assert.True(t, result)
|
assert.True(t, result)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func TestPrivilegeHandler_Fetch_Sends_Authorization_Header_When_APIKey_Set(t *testing.T) {
|
||||||
|
var receivedAuth string
|
||||||
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
receivedAuth = r.Header.Get("Authorization")
|
||||||
|
_, _ = w.Write([]byte("{}"))
|
||||||
|
}))
|
||||||
|
defer server.Close()
|
||||||
|
|
||||||
|
handler := New(
|
||||||
|
WithBaseURL(server.URL),
|
||||||
|
WithAPIKey("my-secret-key"),
|
||||||
|
)
|
||||||
|
|
||||||
|
err := handler.Fetch()
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Equal(t, "Bearer my-secret-key", receivedAuth)
|
||||||
|
}
|
||||||
|
|
||||||
|
func TestPrivilegeHandler_Fetch_No_Authorization_Header_Without_APIKey(t *testing.T) {
|
||||||
|
var receivedAuth string
|
||||||
|
server := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||||
|
receivedAuth = r.Header.Get("Authorization")
|
||||||
|
_, _ = w.Write([]byte("{}"))
|
||||||
|
}))
|
||||||
|
defer server.Close()
|
||||||
|
|
||||||
|
handler := New(WithBaseURL(server.URL))
|
||||||
|
|
||||||
|
err := handler.Fetch()
|
||||||
|
assert.NoError(t, err)
|
||||||
|
assert.Empty(t, receivedAuth)
|
||||||
|
}
|
||||||
|
|
||||||
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)
|
||||||
|
|||||||
Reference in New Issue
Block a user