[MM-63595] Add model structs for Access Control Policies (#30589)
Этот коммит содержится в:
коммит произвёл
GitHub
родитель
2108216818
Коммит
c44c139c9e
@@ -8468,6 +8468,38 @@
|
||||
"id": "model.access.is_valid.user_id.app_error",
|
||||
"translation": "Invalid user id."
|
||||
},
|
||||
{
|
||||
"id": "model.access_policy.is_valid.id.app_error",
|
||||
"translation": "Invalid policyy id."
|
||||
},
|
||||
{
|
||||
"id": "model.access_policy.is_valid.imports.app_error",
|
||||
"translation": "Invalid imports for the policy."
|
||||
},
|
||||
{
|
||||
"id": "model.access_policy.is_valid.name.app_error",
|
||||
"translation": "Invalid name for the policy."
|
||||
},
|
||||
{
|
||||
"id": "model.access_policy.is_valid.revision.app_error",
|
||||
"translation": "Invalid policy revision."
|
||||
},
|
||||
{
|
||||
"id": "model.access_policy.is_valid.rules.app_error",
|
||||
"translation": "Rule(s) is not valid."
|
||||
},
|
||||
{
|
||||
"id": "model.access_policy.is_valid.rules_imports.app_error",
|
||||
"translation": "Policy must either import or define rules."
|
||||
},
|
||||
{
|
||||
"id": "model.access_policy.is_valid.type.app_error",
|
||||
"translation": "Invalid policy type."
|
||||
},
|
||||
{
|
||||
"id": "model.access_policy.is_valid.version.app_error",
|
||||
"translation": "Version is not valid for this access control policy."
|
||||
},
|
||||
{
|
||||
"id": "model.acknowledgement.is_valid.post_id.app_error",
|
||||
"translation": "Invalid post id."
|
||||
|
||||
@@ -29,6 +29,7 @@ require (
|
||||
github.com/tinylib/msgp v1.2.5
|
||||
github.com/vmihailenco/msgpack/v5 v5.4.1
|
||||
golang.org/x/crypto v0.32.0
|
||||
golang.org/x/mod v0.22.0
|
||||
golang.org/x/net v0.34.0
|
||||
golang.org/x/oauth2 v0.25.0
|
||||
golang.org/x/text v0.21.0
|
||||
@@ -66,7 +67,6 @@ require (
|
||||
github.com/wiggin77/merror v1.0.5 // indirect
|
||||
github.com/wiggin77/srslog v1.0.1 // indirect
|
||||
github.com/xtgo/uuid v0.0.0-20140804021211-a0b114877d4c // indirect
|
||||
golang.org/x/mod v0.22.0 // indirect
|
||||
golang.org/x/sync v0.10.0 // indirect
|
||||
golang.org/x/sys v0.29.0 // indirect
|
||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250124145028-65684f501c47 // indirect
|
||||
|
||||
105
server/public/model/access_policy.go
Обычный файл
105
server/public/model/access_policy.go
Обычный файл
@@ -0,0 +1,105 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
package model
|
||||
|
||||
import (
|
||||
"slices"
|
||||
|
||||
"golang.org/x/mod/semver"
|
||||
)
|
||||
|
||||
const (
|
||||
AccessControlPolicyTypeParent = "parent"
|
||||
AccessControlPolicyTypeChannel = "channel"
|
||||
|
||||
MaxPolicyNameLength = 128
|
||||
|
||||
AccessControlPolicyVersionV0_1 = "v0.1"
|
||||
)
|
||||
|
||||
// ParentPolicy is a augmented version of AccessPolicy to be used in
|
||||
// system console and API responses.
|
||||
type ParentPolicy struct {
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Attributes map[string]string `json:"attributes"`
|
||||
Children []*AccessControlPolicy `json:"children"`
|
||||
}
|
||||
|
||||
type AccessControlPolicy struct {
|
||||
ID string `json:"id"`
|
||||
Name string `json:"name"`
|
||||
Type string `json:"type"`
|
||||
Active bool `json:"active"`
|
||||
CreateAt int64 `json:"create_at"`
|
||||
|
||||
Revision int `json:"revision"`
|
||||
Version string `json:"version"`
|
||||
|
||||
Imports []string `json:"imports"`
|
||||
Rules []AccessControlPolicyRule `json:"rules"`
|
||||
|
||||
Props map[string]any `json:"props"` // add auto-sync property here, also maybe the attributes being used in the expression
|
||||
}
|
||||
|
||||
type AccessControlPolicyRule struct {
|
||||
Actions []string `json:"actions"`
|
||||
Expression string `json:"expression"`
|
||||
}
|
||||
|
||||
func (p *AccessControlPolicy) IsValid() *AppError {
|
||||
switch p.Version {
|
||||
case AccessControlPolicyVersionV0_1:
|
||||
return p.accessPolicyVersionV0_1()
|
||||
default:
|
||||
return NewAppError("AccessControlPolicy.IsValid", "model.access_policy.is_valid.version.app_error", nil, "", 400)
|
||||
}
|
||||
}
|
||||
|
||||
func (p *AccessControlPolicy) accessPolicyVersionV0_1() *AppError {
|
||||
if !slices.Contains([]string{AccessControlPolicyTypeParent, AccessControlPolicyTypeChannel}, p.Type) {
|
||||
return NewAppError("AccessControlPolicy.IsValid", "model.access_policy.is_valid.type.app_error", nil, "", 400)
|
||||
}
|
||||
|
||||
if !IsValidId(p.ID) {
|
||||
return NewAppError("AccessControlPolicy.IsValid", "model.access_policy.is_valid.id.app_error", nil, "", 400)
|
||||
}
|
||||
|
||||
if p.Type == AccessControlPolicyTypeParent && (p.Name == "" || len(p.Name) > MaxPolicyNameLength) {
|
||||
return NewAppError("AccessControlPolicy.IsValid", "model.access_policy.is_valid.name.app_error", nil, "", 400)
|
||||
}
|
||||
|
||||
if p.Revision < 0 {
|
||||
return NewAppError("AccessControlPolicy.IsValid", "model.access_policy.is_valid.revision.app_error", nil, "", 400)
|
||||
}
|
||||
|
||||
if !semver.IsValid(p.Version) {
|
||||
return NewAppError("AccessControlPolicy.IsValid", "model.access_policy.is_valid.version.app_error", nil, "", 400)
|
||||
}
|
||||
|
||||
switch p.Type {
|
||||
case AccessControlPolicyTypeParent:
|
||||
if len(p.Rules) == 0 {
|
||||
return NewAppError("AccessControlPolicy.IsValid", "model.access_policy.is_valid.rules.app_error", nil, "", 400)
|
||||
}
|
||||
|
||||
if len(p.Imports) > 0 {
|
||||
return NewAppError("AccessControlPolicy.IsValid", "model.access_policy.is_valid.imports.app_error", nil, "", 400)
|
||||
}
|
||||
case AccessControlPolicyTypeChannel:
|
||||
if len(p.Rules) == 0 && len(p.Imports) == 0 {
|
||||
return NewAppError("AccessControlPolicy.IsValid", "model.access_policy.is_valid.rules_imports.app_error", nil, "", 400)
|
||||
}
|
||||
|
||||
if len(p.Rules) == 0 {
|
||||
return NewAppError("AccessControlPolicy.IsValid", "model.access_policy.is_valid.rules.app_error", nil, "", 400)
|
||||
}
|
||||
|
||||
if len(p.Imports) > 1 {
|
||||
return NewAppError("AccessControlPolicy.IsValid", "model.access_policy.is_valid.imports.app_error", nil, "", 400)
|
||||
}
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
214
server/public/model/access_policy_test.go
Обычный файл
214
server/public/model/access_policy_test.go
Обычный файл
@@ -0,0 +1,214 @@
|
||||
// Copyright (c) 2015-present Mattermost, Inc. All Rights Reserved.
|
||||
// See LICENSE.txt for license information.
|
||||
|
||||
package model
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestAccessPolicyVersionV0_1(t *testing.T) {
|
||||
t.Run("invalid type", func(t *testing.T) {
|
||||
policy := &AccessControlPolicy{
|
||||
ID: "policy_id",
|
||||
Type: "invalid_type",
|
||||
Name: "Test Policy",
|
||||
Revision: 1,
|
||||
Version: AccessControlPolicyVersionV0_1,
|
||||
Rules: []AccessControlPolicyRule{{Actions: []string{"read"}, Expression: "user.role == 'admin'"}},
|
||||
}
|
||||
|
||||
err := policy.accessPolicyVersionV0_1()
|
||||
require.NotNil(t, err, "Should return error for invalid type")
|
||||
require.Equal(t, "model.access_policy.is_valid.type.app_error", err.Id)
|
||||
})
|
||||
|
||||
t.Run("invalid ID", func(t *testing.T) {
|
||||
policy := &AccessControlPolicy{
|
||||
ID: "",
|
||||
Type: AccessControlPolicyTypeParent,
|
||||
Name: "Test Policy",
|
||||
Revision: 1,
|
||||
Version: AccessControlPolicyVersionV0_1,
|
||||
Rules: []AccessControlPolicyRule{{Actions: []string{"read"}, Expression: "user.role == 'admin'"}},
|
||||
}
|
||||
|
||||
err := policy.accessPolicyVersionV0_1()
|
||||
require.NotNil(t, err, "Should return error for invalid ID")
|
||||
require.Equal(t, "model.access_policy.is_valid.id.app_error", err.Id)
|
||||
})
|
||||
|
||||
t.Run("parent policy with empty name", func(t *testing.T) {
|
||||
policy := &AccessControlPolicy{
|
||||
ID: NewId(),
|
||||
Type: AccessControlPolicyTypeParent,
|
||||
Name: "",
|
||||
Revision: 1,
|
||||
Version: AccessControlPolicyVersionV0_1,
|
||||
Rules: []AccessControlPolicyRule{{Actions: []string{"read"}, Expression: "user.role == 'admin'"}},
|
||||
}
|
||||
|
||||
err := policy.accessPolicyVersionV0_1()
|
||||
require.NotNil(t, err, "Should return error for empty name in parent policy")
|
||||
require.Equal(t, "model.access_policy.is_valid.name.app_error", err.Id)
|
||||
})
|
||||
|
||||
t.Run("parent policy with too long name", func(t *testing.T) {
|
||||
longName := ""
|
||||
for i := 0; i <= MaxPolicyNameLength; i++ {
|
||||
longName += "a"
|
||||
}
|
||||
|
||||
policy := &AccessControlPolicy{
|
||||
ID: NewId(),
|
||||
Type: AccessControlPolicyTypeParent,
|
||||
Name: longName,
|
||||
Revision: 1,
|
||||
Version: AccessControlPolicyVersionV0_1,
|
||||
Rules: []AccessControlPolicyRule{{Actions: []string{"read"}, Expression: "user.role == 'admin'"}},
|
||||
}
|
||||
|
||||
err := policy.accessPolicyVersionV0_1()
|
||||
require.NotNil(t, err, "Should return error for too long name in parent policy")
|
||||
require.Equal(t, "model.access_policy.is_valid.name.app_error", err.Id)
|
||||
})
|
||||
|
||||
t.Run("negative revision", func(t *testing.T) {
|
||||
policy := &AccessControlPolicy{
|
||||
ID: NewId(),
|
||||
Type: AccessControlPolicyTypeParent,
|
||||
Name: "Test Policy",
|
||||
Revision: -1,
|
||||
Version: AccessControlPolicyVersionV0_1,
|
||||
Rules: []AccessControlPolicyRule{{Actions: []string{"read"}, Expression: "user.role == 'admin'"}},
|
||||
}
|
||||
|
||||
err := policy.accessPolicyVersionV0_1()
|
||||
require.NotNil(t, err, "Should return error for negative revision")
|
||||
require.Equal(t, "model.access_policy.is_valid.revision.app_error", err.Id)
|
||||
})
|
||||
|
||||
t.Run("invalid version", func(t *testing.T) {
|
||||
policy := &AccessControlPolicy{
|
||||
ID: NewId(),
|
||||
Type: AccessControlPolicyTypeParent,
|
||||
Name: "Test Policy",
|
||||
Revision: 1,
|
||||
Version: "invalid-version",
|
||||
Rules: []AccessControlPolicyRule{{Actions: []string{"read"}, Expression: "user.role == 'admin'"}},
|
||||
}
|
||||
|
||||
err := policy.accessPolicyVersionV0_1()
|
||||
require.NotNil(t, err, "Should return error for invalid version")
|
||||
require.Equal(t, "model.access_policy.is_valid.version.app_error", err.Id)
|
||||
})
|
||||
|
||||
t.Run("parent policy with no rules", func(t *testing.T) {
|
||||
policy := &AccessControlPolicy{
|
||||
ID: NewId(),
|
||||
Type: AccessControlPolicyTypeParent,
|
||||
Name: "Test Policy",
|
||||
Revision: 1,
|
||||
Version: AccessControlPolicyVersionV0_1,
|
||||
Rules: []AccessControlPolicyRule{},
|
||||
}
|
||||
|
||||
err := policy.accessPolicyVersionV0_1()
|
||||
require.NotNil(t, err, "Should return error for parent policy with no rules")
|
||||
require.Equal(t, "model.access_policy.is_valid.rules.app_error", err.Id)
|
||||
})
|
||||
|
||||
t.Run("parent policy with imports", func(t *testing.T) {
|
||||
policy := &AccessControlPolicy{
|
||||
ID: NewId(),
|
||||
Type: AccessControlPolicyTypeParent,
|
||||
Name: "Test Policy",
|
||||
Revision: 1,
|
||||
Version: AccessControlPolicyVersionV0_1,
|
||||
Rules: []AccessControlPolicyRule{{Actions: []string{"read"}, Expression: "user.role == 'admin'"}},
|
||||
Imports: []string{"some_import"},
|
||||
}
|
||||
|
||||
err := policy.accessPolicyVersionV0_1()
|
||||
require.NotNil(t, err, "Should return error for parent policy with imports")
|
||||
require.Equal(t, "model.access_policy.is_valid.imports.app_error", err.Id)
|
||||
})
|
||||
|
||||
t.Run("channel policy with no rules", func(t *testing.T) {
|
||||
policy := &AccessControlPolicy{
|
||||
ID: NewId(),
|
||||
Type: AccessControlPolicyTypeChannel,
|
||||
Name: "Test Policy",
|
||||
Revision: 1,
|
||||
Version: AccessControlPolicyVersionV0_1,
|
||||
Rules: []AccessControlPolicyRule{},
|
||||
Imports: []string{"parent_policy_id"},
|
||||
}
|
||||
|
||||
err := policy.accessPolicyVersionV0_1()
|
||||
require.NotNil(t, err, "Should return error for channel policy with no rules")
|
||||
require.Equal(t, "model.access_policy.is_valid.rules.app_error", err.Id)
|
||||
})
|
||||
|
||||
t.Run("channel policy with no imports", func(t *testing.T) {
|
||||
policy := &AccessControlPolicy{
|
||||
ID: NewId(),
|
||||
Type: AccessControlPolicyTypeChannel,
|
||||
Name: "Test Policy",
|
||||
Revision: 1,
|
||||
Version: AccessControlPolicyVersionV0_1,
|
||||
Rules: []AccessControlPolicyRule{{Actions: []string{"read"}, Expression: "user.role == 'admin'"}},
|
||||
Imports: []string{},
|
||||
}
|
||||
|
||||
err := policy.accessPolicyVersionV0_1()
|
||||
require.Nil(t, err, "Should not return error for channel policy with no imports")
|
||||
})
|
||||
|
||||
t.Run("channel policy with multiple imports", func(t *testing.T) {
|
||||
policy := &AccessControlPolicy{
|
||||
ID: NewId(),
|
||||
Type: AccessControlPolicyTypeChannel,
|
||||
Name: "Test Policy",
|
||||
Revision: 1,
|
||||
Version: AccessControlPolicyVersionV0_1,
|
||||
Rules: []AccessControlPolicyRule{{Actions: []string{"read"}, Expression: "user.role == 'admin'"}},
|
||||
Imports: []string{"parent_policy_id1", "parent_policy_id2"},
|
||||
}
|
||||
|
||||
err := policy.accessPolicyVersionV0_1()
|
||||
require.NotNil(t, err, "Should return error for channel policy with multiple imports")
|
||||
require.Equal(t, "model.access_policy.is_valid.imports.app_error", err.Id)
|
||||
})
|
||||
|
||||
t.Run("valid parent policy", func(t *testing.T) {
|
||||
policy := &AccessControlPolicy{
|
||||
ID: NewId(),
|
||||
Type: AccessControlPolicyTypeParent,
|
||||
Name: "Test Policy",
|
||||
Revision: 1,
|
||||
Version: "v0.1",
|
||||
Rules: []AccessControlPolicyRule{{Actions: []string{"read"}, Expression: "user.role == 'admin'"}},
|
||||
}
|
||||
|
||||
err := policy.accessPolicyVersionV0_1()
|
||||
require.Nil(t, err, "Should not return error for valid parent policy")
|
||||
})
|
||||
|
||||
t.Run("valid channel policy", func(t *testing.T) {
|
||||
policy := &AccessControlPolicy{
|
||||
ID: NewId(),
|
||||
Type: AccessControlPolicyTypeChannel,
|
||||
Name: "Test Policy",
|
||||
Revision: 1,
|
||||
Version: "v0.1",
|
||||
Rules: []AccessControlPolicyRule{{Actions: []string{"read"}, Expression: "user.role == 'admin'"}},
|
||||
Imports: []string{"parent_policy_id"},
|
||||
}
|
||||
|
||||
err := policy.accessPolicyVersionV0_1()
|
||||
require.Nil(t, err, "Should not return error for valid channel policy")
|
||||
})
|
||||
}
|
||||
Ссылка в новой задаче
Block a user