[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",
|
"id": "model.access.is_valid.user_id.app_error",
|
||||||
"translation": "Invalid user id."
|
"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",
|
"id": "model.acknowledgement.is_valid.post_id.app_error",
|
||||||
"translation": "Invalid post id."
|
"translation": "Invalid post id."
|
||||||
|
|||||||
@@ -29,6 +29,7 @@ require (
|
|||||||
github.com/tinylib/msgp v1.2.5
|
github.com/tinylib/msgp v1.2.5
|
||||||
github.com/vmihailenco/msgpack/v5 v5.4.1
|
github.com/vmihailenco/msgpack/v5 v5.4.1
|
||||||
golang.org/x/crypto v0.32.0
|
golang.org/x/crypto v0.32.0
|
||||||
|
golang.org/x/mod v0.22.0
|
||||||
golang.org/x/net v0.34.0
|
golang.org/x/net v0.34.0
|
||||||
golang.org/x/oauth2 v0.25.0
|
golang.org/x/oauth2 v0.25.0
|
||||||
golang.org/x/text v0.21.0
|
golang.org/x/text v0.21.0
|
||||||
@@ -66,7 +67,6 @@ require (
|
|||||||
github.com/wiggin77/merror v1.0.5 // indirect
|
github.com/wiggin77/merror v1.0.5 // indirect
|
||||||
github.com/wiggin77/srslog v1.0.1 // indirect
|
github.com/wiggin77/srslog v1.0.1 // indirect
|
||||||
github.com/xtgo/uuid v0.0.0-20140804021211-a0b114877d4c // 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/sync v0.10.0 // indirect
|
||||||
golang.org/x/sys v0.29.0 // indirect
|
golang.org/x/sys v0.29.0 // indirect
|
||||||
google.golang.org/genproto/googleapis/rpc v0.0.0-20250124145028-65684f501c47 // 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