mirror of
https://github.com/absmach/magistrala.git
synced 2026-06-23 04:10:28 +00:00
SMQ-2758 - Add option Auth to call webhook for only for certain authz (#2763)
Signed-off-by: nyagamunene <stevenyaga2014@gmail.com>
This commit is contained in:
@@ -102,6 +102,7 @@ The service is configured using the environment variables presented in the follo
|
||||
| SMQ_AUTH_CALLOUT_CA_CERT | Path to CA certificate file | "" |
|
||||
| SMQ_AUTH_CALLOUT_CERT | Path to client certificate file | "" |
|
||||
| SMQ_AUTH_CALLOUT_KEY | Path to client key file | "" |
|
||||
| SMQ_AUTH_CALLOUT_INVOKE_PERMISSIONS | Invoke callout if the authorization permission matches any of the given permissions. | "" |
|
||||
|
||||
## Deployment
|
||||
|
||||
|
||||
+23
-7
@@ -17,9 +17,10 @@ import (
|
||||
)
|
||||
|
||||
type callback struct {
|
||||
httpClient *http.Client
|
||||
urls []string
|
||||
method string
|
||||
httpClient *http.Client
|
||||
urls []string
|
||||
method string
|
||||
allowedPermission map[string]struct{}
|
||||
}
|
||||
|
||||
// CallBack send auth request to an external service.
|
||||
@@ -30,7 +31,7 @@ type CallBack interface {
|
||||
}
|
||||
|
||||
// NewCallback creates a new instance of CallBack.
|
||||
func NewCallback(httpClient *http.Client, method string, urls []string) (CallBack, error) {
|
||||
func NewCallback(httpClient *http.Client, method string, urls []string, permissions []string) (CallBack, error) {
|
||||
if httpClient == nil {
|
||||
httpClient = http.DefaultClient
|
||||
}
|
||||
@@ -38,10 +39,16 @@ func NewCallback(httpClient *http.Client, method string, urls []string) (CallBac
|
||||
return nil, fmt.Errorf("unsupported auth callback method: %s", method)
|
||||
}
|
||||
|
||||
allowedPermission := make(map[string]struct{})
|
||||
for _, permission := range permissions {
|
||||
allowedPermission[permission] = struct{}{}
|
||||
}
|
||||
|
||||
return &callback{
|
||||
httpClient: httpClient,
|
||||
urls: urls,
|
||||
method: method,
|
||||
httpClient: httpClient,
|
||||
urls: urls,
|
||||
method: method,
|
||||
allowedPermission: allowedPermission,
|
||||
}, nil
|
||||
}
|
||||
|
||||
@@ -50,6 +57,15 @@ func (c *callback) Authorize(ctx context.Context, pr policies.Policy) error {
|
||||
return nil
|
||||
}
|
||||
|
||||
// Check if the permission is in the allowed list
|
||||
// Otherwise, only call webhook if the permission is in the map
|
||||
if len(c.allowedPermission) > 0 {
|
||||
_, exists := c.allowedPermission[pr.Permission]
|
||||
if !exists {
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
payload := map[string]string{
|
||||
"domain": pr.Domain,
|
||||
"subject": pr.Subject,
|
||||
|
||||
+59
-7
@@ -71,7 +71,7 @@ func TestCallback_Authorize(t *testing.T) {
|
||||
}))
|
||||
defer ts.Close()
|
||||
|
||||
cb, err := auth.NewCallback(http.DefaultClient, tc.method, []string{ts.URL})
|
||||
cb, err := auth.NewCallback(http.DefaultClient, tc.method, []string{ts.URL}, []string{})
|
||||
assert.NoError(t, err)
|
||||
err = cb.Authorize(context.Background(), policy)
|
||||
|
||||
@@ -96,21 +96,21 @@ func TestCallback_MultipleURLs(t *testing.T) {
|
||||
}))
|
||||
defer ts2.Close()
|
||||
|
||||
cb, err := auth.NewCallback(http.DefaultClient, http.MethodPost, []string{ts1.URL, ts2.URL})
|
||||
cb, err := auth.NewCallback(http.DefaultClient, http.MethodPost, []string{ts1.URL, ts2.URL}, []string{})
|
||||
assert.NoError(t, err)
|
||||
err = cb.Authorize(context.Background(), policies.Policy{})
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestCallback_InvalidURL(t *testing.T) {
|
||||
cb, err := auth.NewCallback(http.DefaultClient, http.MethodPost, []string{"http://invalid-url"})
|
||||
cb, err := auth.NewCallback(http.DefaultClient, http.MethodPost, []string{"http://invalid-url"}, []string{})
|
||||
assert.NoError(t, err)
|
||||
err = cb.Authorize(context.Background(), policies.Policy{})
|
||||
assert.Error(t, err)
|
||||
}
|
||||
|
||||
func TestCallback_InvalidMethod(t *testing.T) {
|
||||
_, err := auth.NewCallback(http.DefaultClient, "invalid-method", []string{"http://example.com"})
|
||||
_, err := auth.NewCallback(http.DefaultClient, "invalid-method", []string{"http://example.com"}, []string{})
|
||||
assert.Error(t, err)
|
||||
}
|
||||
|
||||
@@ -123,21 +123,73 @@ func TestCallback_CancelledContext(t *testing.T) {
|
||||
ctx, cancel := context.WithCancel(context.Background())
|
||||
cancel()
|
||||
|
||||
cb, err := auth.NewCallback(http.DefaultClient, http.MethodPost, []string{ts.URL})
|
||||
cb, err := auth.NewCallback(http.DefaultClient, http.MethodPost, []string{ts.URL}, []string{})
|
||||
assert.NoError(t, err)
|
||||
err = cb.Authorize(ctx, policies.Policy{})
|
||||
assert.Error(t, err)
|
||||
}
|
||||
|
||||
func TestNewCallback_NilClient(t *testing.T) {
|
||||
cb, err := auth.NewCallback(nil, http.MethodPost, []string{"test"})
|
||||
cb, err := auth.NewCallback(nil, http.MethodPost, []string{"test"}, []string{})
|
||||
assert.NoError(t, err)
|
||||
assert.NotNil(t, cb)
|
||||
}
|
||||
|
||||
func TestCallback_NoURL(t *testing.T) {
|
||||
cb, err := auth.NewCallback(http.DefaultClient, http.MethodPost, []string{})
|
||||
cb, err := auth.NewCallback(http.DefaultClient, http.MethodPost, []string{}, []string{})
|
||||
assert.NoError(t, err)
|
||||
err = cb.Authorize(context.Background(), policies.Policy{})
|
||||
assert.NoError(t, err)
|
||||
}
|
||||
|
||||
func TestCallback_PermissionFiltering(t *testing.T) {
|
||||
webhookCalled := false
|
||||
|
||||
ts := httptest.NewServer(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
|
||||
webhookCalled = true
|
||||
w.WriteHeader(http.StatusOK)
|
||||
}))
|
||||
defer ts.Close()
|
||||
|
||||
t.Run("allowed permission", func(t *testing.T) {
|
||||
webhookCalled = false
|
||||
allowedPermissions := []string{"create_client", "delete_channel"}
|
||||
|
||||
cb, err := auth.NewCallback(http.DefaultClient, http.MethodPost, []string{ts.URL}, allowedPermissions)
|
||||
assert.NoError(t, err)
|
||||
|
||||
err = cb.Authorize(context.Background(), policies.Policy{
|
||||
Permission: "create_client",
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
assert.True(t, webhookCalled, "webhook should be called for allowed permission")
|
||||
})
|
||||
|
||||
t.Run("non-allowed permission", func(t *testing.T) {
|
||||
webhookCalled = false
|
||||
allowedPermissions := []string{"create_client", "delete_channel"}
|
||||
|
||||
cb, err := auth.NewCallback(http.DefaultClient, http.MethodPost, []string{ts.URL}, allowedPermissions)
|
||||
assert.NoError(t, err)
|
||||
|
||||
err = cb.Authorize(context.Background(), policies.Policy{
|
||||
Permission: "read_channel",
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
assert.False(t, webhookCalled, "webhook should not be called for non-allowed permission")
|
||||
})
|
||||
|
||||
t.Run("empty allowed permissions", func(t *testing.T) {
|
||||
webhookCalled = false
|
||||
allowedPermissions := []string{}
|
||||
|
||||
cb, err := auth.NewCallback(http.DefaultClient, http.MethodPost, []string{ts.URL}, allowedPermissions)
|
||||
assert.NoError(t, err)
|
||||
|
||||
err = cb.Authorize(context.Background(), policies.Policy{
|
||||
Permission: "any_permission",
|
||||
})
|
||||
assert.NoError(t, err)
|
||||
assert.True(t, webhookCalled, "webhook should be called when allowed permissions list is empty")
|
||||
})
|
||||
}
|
||||
|
||||
+2
-1
@@ -87,6 +87,7 @@ type config struct {
|
||||
AuthCalloutCACert string `env:"SMQ_AUTH_CALLOUT_CA_CERT" envDefault:""`
|
||||
AuthCalloutCert string `env:"SMQ_AUTH_CALLOUT_CERT" envDefault:""`
|
||||
AuthCalloutKey string `env:"SMQ_AUTH_CALLOUT_KEY" envDefault:""`
|
||||
AuthCalloutPermissions []string `env:"SMQ_AUTH_CALLOUT_INVOKE_PERMISSIONS" envDefault:"" envSeparator:","`
|
||||
}
|
||||
|
||||
func main() {
|
||||
@@ -284,7 +285,7 @@ func newService(db *sqlx.DB, tracer trace.Tracer, cfg config, dbConfig pgclient.
|
||||
},
|
||||
Timeout: cfg.AuthCalloutTimeout,
|
||||
}
|
||||
callback, err := auth.NewCallback(httpClient, cfg.AuthCalloutMethod, cfg.AuthCalloutURLs)
|
||||
callback, err := auth.NewCallback(httpClient, cfg.AuthCalloutMethod, cfg.AuthCalloutURLs, cfg.AuthCalloutPermissions)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
@@ -109,6 +109,7 @@ SMQ_AUTH_CALLOUT_TIMEOUT="10s"
|
||||
SMQ_AUTH_CALLOUT_CA_CERT=""
|
||||
SMQ_AUTH_CALLOUT_CERT=""
|
||||
SMQ_AUTH_CALLOUT_KEY=""
|
||||
SMQ_AUTH_CALLOUT_INVOKE_PERMISSIONS=""
|
||||
|
||||
#### Auth Client Config
|
||||
SMQ_AUTH_URL=auth:9001
|
||||
|
||||
@@ -149,6 +149,7 @@ services:
|
||||
SMQ_AUTH_CALLOUT_CA_CERT: ${SMQ_AUTH_CALLOUT_CA_CERT}
|
||||
SMQ_AUTH_CALLOUT_CERT: ${SMQ_AUTH_CALLOUT_CERT}
|
||||
SMQ_AUTH_CALLOUT_KEY: ${SMQ_AUTH_CALLOUT_KEY}
|
||||
SMQ_AUTH_CALLOUT_INVOKE_PERMISSIONS: ${SMQ_AUTH_CALLOUT_INVOKE_PERMISSIONS}
|
||||
ports:
|
||||
- ${SMQ_AUTH_HTTP_PORT}:${SMQ_AUTH_HTTP_PORT}
|
||||
- ${SMQ_AUTH_GRPC_PORT}:${SMQ_AUTH_GRPC_PORT}
|
||||
|
||||
Reference in New Issue
Block a user