mirror of
https://github.com/zitadel/zitadel.git
synced 2025-10-18 08:33:59 +00:00
feat: add some api packages
This commit is contained in:
108
internal/api/auth/authorization.go
Normal file
108
internal/api/auth/authorization.go
Normal file
@@ -0,0 +1,108 @@
|
||||
package auth
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"reflect"
|
||||
"strings"
|
||||
|
||||
"github.com/caos/zitadel/internal/errors"
|
||||
)
|
||||
|
||||
const (
|
||||
authenticated = "authenticated"
|
||||
)
|
||||
|
||||
func CheckUserAuthorization(ctx context.Context, req interface{}, token, orgID string, verifier TokenVerifier, authConfig *Config, requiredAuthOption Option) (context.Context, error) {
|
||||
ctx, err := VerifyTokenAndWriteCtxData(ctx, token, orgID, verifier)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
if requiredAuthOption.Permission == authenticated {
|
||||
return ctx, nil
|
||||
}
|
||||
|
||||
ctx, perms, err := getUserMethodPermissions(ctx, verifier, requiredAuthOption.Permission, authConfig)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = checkUserPermissions(req, perms, requiredAuthOption)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return ctx, nil
|
||||
}
|
||||
|
||||
func checkUserPermissions(req interface{}, userPerms []string, authOpt Option) error {
|
||||
if len(userPerms) == 0 {
|
||||
return errors.ThrowPermissionDenied(nil, "AUTH-5mWD2", "No matching permissions found")
|
||||
}
|
||||
|
||||
if authOpt.CheckParam == "" {
|
||||
return nil
|
||||
}
|
||||
|
||||
if HasGlobalPermission(userPerms) {
|
||||
return nil
|
||||
}
|
||||
|
||||
if hasContextPermission(req, authOpt.CheckParam, userPerms) {
|
||||
return nil
|
||||
}
|
||||
|
||||
return errors.ThrowPermissionDenied(nil, "AUTH-3jknH", "No matching permissions found")
|
||||
}
|
||||
|
||||
func SplitPermission(perm string) (string, string) {
|
||||
splittedPerm := strings.Split(perm, ":")
|
||||
if len(splittedPerm) == 1 {
|
||||
return splittedPerm[0], ""
|
||||
}
|
||||
return splittedPerm[0], splittedPerm[1]
|
||||
}
|
||||
|
||||
func hasContextPermission(req interface{}, fieldName string, permissions []string) bool {
|
||||
for _, perm := range permissions {
|
||||
_, ctxID := SplitPermission(perm)
|
||||
if checkPermissionContext(req, fieldName, ctxID) {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func checkPermissionContext(req interface{}, fieldName, roleContextID string) bool {
|
||||
field := getFieldFromReq(req, fieldName)
|
||||
return field != "" && field == roleContextID
|
||||
}
|
||||
|
||||
func getFieldFromReq(req interface{}, field string) string {
|
||||
v := reflect.Indirect(reflect.ValueOf(req)).FieldByName(field)
|
||||
if reflect.ValueOf(v).IsZero() {
|
||||
return ""
|
||||
}
|
||||
return fmt.Sprintf("%v", v.Interface())
|
||||
}
|
||||
|
||||
func HasGlobalPermission(perms []string) bool {
|
||||
for _, perm := range perms {
|
||||
_, ctxID := SplitPermission(perm)
|
||||
if ctxID == "" {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
||||
|
||||
func GetPermissionCtxIDs(perms []string) []string {
|
||||
ctxIDs := make([]string, 0)
|
||||
for _, perm := range perms {
|
||||
_, ctxID := SplitPermission(perm)
|
||||
if ctxID != "" {
|
||||
ctxIDs = append(ctxIDs, ctxID)
|
||||
}
|
||||
}
|
||||
return ctxIDs
|
||||
}
|
280
internal/api/auth/authorization_test.go
Normal file
280
internal/api/auth/authorization_test.go
Normal file
@@ -0,0 +1,280 @@
|
||||
package auth
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"github.com/caos/zitadel/internal/errors"
|
||||
)
|
||||
|
||||
type TestRequest struct {
|
||||
Test string
|
||||
}
|
||||
|
||||
func Test_CheckUserPermissions(t *testing.T) {
|
||||
type args struct {
|
||||
req *TestRequest
|
||||
perms []string
|
||||
authOpt Option
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "no permissions",
|
||||
args: args{
|
||||
req: &TestRequest{},
|
||||
perms: []string{},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "has permission and no context requested",
|
||||
args: args{
|
||||
req: &TestRequest{},
|
||||
perms: []string{"project.read"},
|
||||
authOpt: Option{CheckParam: ""},
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "context requested and has global permission",
|
||||
args: args{
|
||||
req: &TestRequest{Test: "Test"},
|
||||
perms: []string{"project.read", "project.read:1"},
|
||||
authOpt: Option{CheckParam: "Test"},
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "context requested and has specific permission",
|
||||
args: args{
|
||||
req: &TestRequest{Test: "Test"},
|
||||
perms: []string{"project.read:Test"},
|
||||
authOpt: Option{CheckParam: "Test"},
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
{
|
||||
name: "context requested and has no permission",
|
||||
args: args{
|
||||
req: &TestRequest{Test: "Hodor"},
|
||||
perms: []string{"project.read:Test"},
|
||||
authOpt: Option{CheckParam: "Test"},
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
err := checkUserPermissions(tt.args.req, tt.args.perms, tt.args.authOpt)
|
||||
if tt.wantErr && err == nil {
|
||||
t.Errorf("got wrong result, should get err: actual: %v ", err)
|
||||
}
|
||||
|
||||
if !tt.wantErr && err != nil {
|
||||
t.Errorf("shouldn't get err: %v ", err)
|
||||
}
|
||||
|
||||
if tt.wantErr && !errors.IsPermissionDenied(err) {
|
||||
t.Errorf("got wrong err: %v ", err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_SplitPermission(t *testing.T) {
|
||||
type args struct {
|
||||
perm string
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
permName string
|
||||
permCtxID string
|
||||
}{
|
||||
{
|
||||
name: "permission with context id",
|
||||
args: args{
|
||||
perm: "project.read:ctxID",
|
||||
},
|
||||
permName: "project.read",
|
||||
permCtxID: "ctxID",
|
||||
},
|
||||
{
|
||||
name: "permission without context id",
|
||||
args: args{
|
||||
perm: "project.read",
|
||||
},
|
||||
permName: "project.read",
|
||||
permCtxID: "",
|
||||
},
|
||||
{
|
||||
name: "permission to many parts",
|
||||
args: args{
|
||||
perm: "project.read:1:0",
|
||||
},
|
||||
permName: "project.read",
|
||||
permCtxID: "1",
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
name, id := SplitPermission(tt.args.perm)
|
||||
if name != tt.permName {
|
||||
t.Errorf("got wrong result on name, expecting: %v, actual: %v ", tt.permName, name)
|
||||
}
|
||||
if id != tt.permCtxID {
|
||||
t.Errorf("got wrong result on id, expecting: %v, actual: %v ", tt.permCtxID, id)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_HasContextPermission(t *testing.T) {
|
||||
type args struct {
|
||||
req *TestRequest
|
||||
fieldname string
|
||||
perms []string
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
result bool
|
||||
}{
|
||||
{
|
||||
name: "existing context permission",
|
||||
args: args{
|
||||
req: &TestRequest{Test: "right"},
|
||||
fieldname: "Test",
|
||||
perms: []string{"test:wrong", "test:right"},
|
||||
},
|
||||
result: true,
|
||||
},
|
||||
{
|
||||
name: "not existing context permission",
|
||||
args: args{
|
||||
req: &TestRequest{Test: "test"},
|
||||
fieldname: "Test",
|
||||
perms: []string{"test:wrong", "test:wrong2"},
|
||||
},
|
||||
result: false,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
result := hasContextPermission(tt.args.req, tt.args.fieldname, tt.args.perms)
|
||||
if result != tt.result {
|
||||
t.Errorf("got wrong result, expecting: %v, actual: %v ", tt.result, result)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_GetFieldFromReq(t *testing.T) {
|
||||
type args struct {
|
||||
req *TestRequest
|
||||
fieldname string
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
result string
|
||||
}{
|
||||
{
|
||||
name: "existing field",
|
||||
args: args{
|
||||
req: &TestRequest{Test: "TestValue"},
|
||||
fieldname: "Test",
|
||||
},
|
||||
result: "TestValue",
|
||||
},
|
||||
{
|
||||
name: "not existing field",
|
||||
args: args{
|
||||
req: &TestRequest{Test: "TestValue"},
|
||||
fieldname: "Test2",
|
||||
},
|
||||
result: "",
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
result := getFieldFromReq(tt.args.req, tt.args.fieldname)
|
||||
if result != tt.result {
|
||||
t.Errorf("got wrong result, expecting: %v, actual: %v ", tt.result, result)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_HasGlobalPermission(t *testing.T) {
|
||||
|
||||
type args struct {
|
||||
perms []string
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
result bool
|
||||
}{
|
||||
{
|
||||
name: "global perm existing",
|
||||
args: args{
|
||||
perms: []string{"perm:1", "perm:2", "perm"},
|
||||
},
|
||||
result: true,
|
||||
},
|
||||
{
|
||||
name: "global perm not existing",
|
||||
args: args{
|
||||
perms: []string{"perm:1", "perm:2", "perm:3"},
|
||||
},
|
||||
result: false,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
result := HasGlobalPermission(tt.args.perms)
|
||||
if result != tt.result {
|
||||
t.Errorf("got wrong result, expecting: %v, actual: %v ", tt.result, result)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_GetPermissionCtxIDs(t *testing.T) {
|
||||
|
||||
type args struct {
|
||||
perms []string
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
result []string
|
||||
}{
|
||||
{
|
||||
name: "no specific permission",
|
||||
args: args{
|
||||
perms: []string{"perm"},
|
||||
},
|
||||
result: []string{},
|
||||
},
|
||||
{
|
||||
name: "ctx id",
|
||||
args: args{
|
||||
perms: []string{"perm:1", "perm", "perm:3"},
|
||||
},
|
||||
result: []string{"1", "3"},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
result := GetPermissionCtxIDs(tt.args.perms)
|
||||
if !EqualStringArray(result, tt.result) {
|
||||
t.Errorf("got wrong result, expecting: %v, actual: %v ", tt.result, result)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
26
internal/api/auth/config.go
Normal file
26
internal/api/auth/config.go
Normal file
@@ -0,0 +1,26 @@
|
||||
package auth
|
||||
|
||||
type Config struct {
|
||||
RolePermissionMappings []RoleMapping
|
||||
}
|
||||
|
||||
type RoleMapping struct {
|
||||
Role string
|
||||
Permissions []string
|
||||
}
|
||||
|
||||
type MethodMapping map[string]Option
|
||||
|
||||
type Option struct {
|
||||
Permission string
|
||||
CheckParam string
|
||||
}
|
||||
|
||||
func (a *Config) getPermissionsFromRole(role string) []string {
|
||||
for _, roleMap := range a.RolePermissionMappings {
|
||||
if roleMap.Role == role {
|
||||
return roleMap.Permissions
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
70
internal/api/auth/context.go
Normal file
70
internal/api/auth/context.go
Normal file
@@ -0,0 +1,70 @@
|
||||
package auth
|
||||
|
||||
import (
|
||||
"context"
|
||||
"time"
|
||||
|
||||
"github.com/caos/logging"
|
||||
)
|
||||
|
||||
const (
|
||||
HeaderOrgID = "x-caos-zitadel-orgid"
|
||||
)
|
||||
|
||||
type CtxKeyPermissions struct{}
|
||||
type CtxKeyData struct{}
|
||||
|
||||
type CtxData struct {
|
||||
UserID string
|
||||
OrgID string
|
||||
ProjectID string
|
||||
AgentID string
|
||||
}
|
||||
|
||||
func (ctxData CtxData) IsZero() bool {
|
||||
return ctxData.UserID == "" || ctxData.OrgID == ""
|
||||
}
|
||||
|
||||
type Grants []*Grant
|
||||
|
||||
type Grant struct {
|
||||
OrgID string
|
||||
Roles []string
|
||||
}
|
||||
|
||||
type TokenVerifier interface {
|
||||
VerifyAccessToken(ctx context.Context, token string) (string, string, string, error)
|
||||
ResolveGrants(ctx context.Context, sub, orgID string) ([]*Grant, error)
|
||||
GetProjectIDByClientID(ctx context.Context, clientID string) (string, error)
|
||||
}
|
||||
|
||||
func VerifyTokenAndWriteCtxData(ctx context.Context, token, orgID string, t TokenVerifier) (_ context.Context, err error) {
|
||||
userID, clientID, agentID, err := verifyAccessToken(ctx, token, t)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
projectID, err := t.GetProjectIDByClientID(ctx, clientID)
|
||||
logging.LogWithFields("AUTH-GfAoV", "clientID", clientID).OnError(err).Warn("could not read projectid by clientid")
|
||||
|
||||
return context.WithValue(ctx, CtxKeyData{}, &CtxData{UserID: userID, OrgID: orgID, ProjectID: projectID, AgentID: agentID}), nil
|
||||
}
|
||||
|
||||
func GetCtxData(ctx context.Context) CtxData {
|
||||
if data := ctx.Value(CtxKeyData{}); data != nil {
|
||||
ctxData, ok := data.(*CtxData)
|
||||
if ok {
|
||||
return *ctxData
|
||||
}
|
||||
time.Now()
|
||||
}
|
||||
return CtxData{}
|
||||
}
|
||||
|
||||
func GetPermissionsFromCtx(ctx context.Context) []string {
|
||||
if data := ctx.Value(CtxKeyPermissions{}); data != nil {
|
||||
ctxPermission, _ := data.([]string)
|
||||
return ctxPermission
|
||||
}
|
||||
return nil
|
||||
}
|
61
internal/api/auth/permissions.go
Normal file
61
internal/api/auth/permissions.go
Normal file
@@ -0,0 +1,61 @@
|
||||
package auth
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/caos/zitadel/internal/errors"
|
||||
)
|
||||
|
||||
func getUserMethodPermissions(ctx context.Context, t TokenVerifier, requiredPerm string, authConfig *Config) (context.Context, []string, error) {
|
||||
ctxData := GetCtxData(ctx)
|
||||
if ctxData.IsZero() {
|
||||
return nil, nil, errors.ThrowUnauthenticated(nil, "AUTH-rKLWEH", "context missing")
|
||||
}
|
||||
grants, err := t.ResolveGrants(ctx, ctxData.UserID, ctxData.OrgID)
|
||||
if err != nil {
|
||||
return nil, nil, err
|
||||
}
|
||||
permissions := mapGrantsToPermissions(requiredPerm, grants, authConfig)
|
||||
return context.WithValue(ctx, CtxKeyPermissions{}, permissions), permissions, nil
|
||||
}
|
||||
|
||||
func mapGrantsToPermissions(requiredPerm string, grants []*Grant, authConfig *Config) []string {
|
||||
resolvedPermissions := make([]string, 0)
|
||||
for _, grant := range grants {
|
||||
for _, role := range grant.Roles {
|
||||
resolvedPermissions = mapRoleToPerm(requiredPerm, role, authConfig, resolvedPermissions)
|
||||
}
|
||||
}
|
||||
return resolvedPermissions
|
||||
}
|
||||
|
||||
func mapRoleToPerm(requiredPerm, actualRole string, authConfig *Config, resolvedPermissions []string) []string {
|
||||
roleName, roleContextID := SplitPermission(actualRole)
|
||||
perms := authConfig.getPermissionsFromRole(roleName)
|
||||
|
||||
for _, p := range perms {
|
||||
if p == requiredPerm {
|
||||
p = addRoleContextIDToPerm(p, roleContextID)
|
||||
if !existsPerm(resolvedPermissions, p) {
|
||||
resolvedPermissions = append(resolvedPermissions, p)
|
||||
}
|
||||
}
|
||||
}
|
||||
return resolvedPermissions
|
||||
}
|
||||
|
||||
func addRoleContextIDToPerm(perm, roleContextID string) string {
|
||||
if roleContextID != "" {
|
||||
perm = perm + ":" + roleContextID
|
||||
}
|
||||
return perm
|
||||
}
|
||||
|
||||
func existsPerm(existing []string, perm string) bool {
|
||||
for _, e := range existing {
|
||||
if e == perm {
|
||||
return true
|
||||
}
|
||||
}
|
||||
return false
|
||||
}
|
431
internal/api/auth/permissions_test.go
Normal file
431
internal/api/auth/permissions_test.go
Normal file
@@ -0,0 +1,431 @@
|
||||
package auth
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
caos_errs "github.com/caos/zitadel/internal/errors"
|
||||
)
|
||||
|
||||
func getTestCtx(userID, orgID string) context.Context {
|
||||
return context.WithValue(context.Background(), CtxKeyData{}, &CtxData{UserID: userID, OrgID: orgID})
|
||||
}
|
||||
|
||||
type testVerifier struct {
|
||||
grants []*Grant
|
||||
}
|
||||
|
||||
func (v *testVerifier) VerifyAccessToken(ctx context.Context, token string) (string, string, string, error) {
|
||||
return "", "", "", nil
|
||||
}
|
||||
|
||||
func (v *testVerifier) ResolveGrants(ctx context.Context, sub, orgID string) ([]*Grant, error) {
|
||||
return v.grants, nil
|
||||
}
|
||||
|
||||
func (v *testVerifier) GetProjectIDByClientID(ctx context.Context, clientID string) (string, error) {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
func EqualStringArray(a, b []string) bool {
|
||||
if len(a) != len(b) {
|
||||
return false
|
||||
}
|
||||
for i, v := range a {
|
||||
if v != b[i] {
|
||||
return false
|
||||
}
|
||||
}
|
||||
return true
|
||||
}
|
||||
|
||||
func Test_GetUserMethodPermissions(t *testing.T) {
|
||||
type args struct {
|
||||
ctx context.Context
|
||||
verifier TokenVerifier
|
||||
requiredPerm string
|
||||
authConfig *Config
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
wantErr bool
|
||||
errFunc func(err error) bool
|
||||
result []string
|
||||
}{
|
||||
{
|
||||
name: "Empty Context",
|
||||
args: args{
|
||||
ctx: getTestCtx("", ""),
|
||||
verifier: &testVerifier{grants: []*Grant{&Grant{
|
||||
Roles: []string{"ORG_OWNER"}}}},
|
||||
requiredPerm: "project.read",
|
||||
authConfig: &Config{
|
||||
RolePermissionMappings: []RoleMapping{
|
||||
RoleMapping{
|
||||
Role: "IAM_OWNER",
|
||||
Permissions: []string{"project.read"},
|
||||
},
|
||||
RoleMapping{
|
||||
Role: "ORG_OWNER",
|
||||
Permissions: []string{"org.read", "project.read"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
wantErr: true,
|
||||
errFunc: func(err error) bool {
|
||||
return caos_errs.IsUnauthenticated(err)
|
||||
},
|
||||
result: []string{"project.read"},
|
||||
},
|
||||
{
|
||||
name: "No Grants",
|
||||
args: args{
|
||||
ctx: getTestCtx("", ""),
|
||||
verifier: &testVerifier{grants: []*Grant{}},
|
||||
requiredPerm: "project.read",
|
||||
authConfig: &Config{
|
||||
RolePermissionMappings: []RoleMapping{
|
||||
RoleMapping{
|
||||
Role: "IAM_OWNER",
|
||||
Permissions: []string{"project.read"},
|
||||
},
|
||||
RoleMapping{
|
||||
Role: "ORG_OWNER",
|
||||
Permissions: []string{"org.read", "project.read"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
result: make([]string, 0),
|
||||
},
|
||||
{
|
||||
name: "Get Permissions",
|
||||
args: args{
|
||||
ctx: getTestCtx("userID", "orgID"),
|
||||
verifier: &testVerifier{grants: []*Grant{&Grant{
|
||||
Roles: []string{"ORG_OWNER"}}}},
|
||||
requiredPerm: "project.read",
|
||||
authConfig: &Config{
|
||||
RolePermissionMappings: []RoleMapping{
|
||||
RoleMapping{
|
||||
Role: "IAM_OWNER",
|
||||
Permissions: []string{"project.read"},
|
||||
},
|
||||
RoleMapping{
|
||||
Role: "ORG_OWNER",
|
||||
Permissions: []string{"org.read", "project.read"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
result: []string{"project.read"},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
_, perms, err := getUserMethodPermissions(tt.args.ctx, tt.args.verifier, tt.args.requiredPerm, tt.args.authConfig)
|
||||
|
||||
if tt.wantErr && err == nil {
|
||||
t.Errorf("got wrong result, should get err: actual: %v ", err)
|
||||
}
|
||||
|
||||
if tt.wantErr && !tt.errFunc(err) {
|
||||
t.Errorf("got wrong err: %v ", err)
|
||||
}
|
||||
|
||||
if !tt.wantErr && !EqualStringArray(perms, tt.result) {
|
||||
t.Errorf("got wrong result, expecting: %v, actual: %v ", tt.result, perms)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_MapGrantsToPermissions(t *testing.T) {
|
||||
type args struct {
|
||||
requiredPerm string
|
||||
grants []*Grant
|
||||
authConfig *Config
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
result []string
|
||||
}{
|
||||
{
|
||||
name: "One Role existing perm",
|
||||
args: args{
|
||||
requiredPerm: "project.read",
|
||||
grants: []*Grant{&Grant{
|
||||
Roles: []string{"ORG_OWNER"}}},
|
||||
authConfig: &Config{
|
||||
RolePermissionMappings: []RoleMapping{
|
||||
RoleMapping{
|
||||
Role: "IAM_OWNER",
|
||||
Permissions: []string{"project.read"},
|
||||
},
|
||||
RoleMapping{
|
||||
Role: "ORG_OWNER",
|
||||
Permissions: []string{"org.read", "project.read"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
result: []string{"project.read"},
|
||||
},
|
||||
{
|
||||
name: "One Role not existing perm",
|
||||
args: args{
|
||||
requiredPerm: "project.write",
|
||||
grants: []*Grant{&Grant{
|
||||
Roles: []string{"ORG_OWNER"}}},
|
||||
authConfig: &Config{
|
||||
RolePermissionMappings: []RoleMapping{
|
||||
RoleMapping{
|
||||
Role: "IAM_OWNER",
|
||||
Permissions: []string{"project.read"},
|
||||
},
|
||||
RoleMapping{
|
||||
Role: "ORG_OWNER",
|
||||
Permissions: []string{"org.read", "project.read"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
result: []string{},
|
||||
},
|
||||
{
|
||||
name: "Multiple Roles one existing",
|
||||
args: args{
|
||||
requiredPerm: "project.read",
|
||||
grants: []*Grant{&Grant{
|
||||
Roles: []string{"ORG_OWNER", "IAM_OWNER"}}},
|
||||
authConfig: &Config{
|
||||
RolePermissionMappings: []RoleMapping{
|
||||
RoleMapping{
|
||||
Role: "IAM_OWNER",
|
||||
Permissions: []string{"project.read"},
|
||||
},
|
||||
RoleMapping{
|
||||
Role: "ORG_OWNER",
|
||||
Permissions: []string{"org.read", "project.read"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
result: []string{"project.read"},
|
||||
},
|
||||
{
|
||||
name: "Multiple Roles, global and specific",
|
||||
args: args{
|
||||
requiredPerm: "project.read",
|
||||
grants: []*Grant{&Grant{
|
||||
Roles: []string{"ORG_OWNER", "PROJECT_OWNER:1"}}},
|
||||
authConfig: &Config{
|
||||
RolePermissionMappings: []RoleMapping{
|
||||
RoleMapping{
|
||||
Role: "PROJECT_OWNER",
|
||||
Permissions: []string{"project.read"},
|
||||
},
|
||||
RoleMapping{
|
||||
Role: "ORG_OWNER",
|
||||
Permissions: []string{"org.read", "project.read"},
|
||||
},
|
||||
},
|
||||
},
|
||||
},
|
||||
result: []string{"project.read", "project.read:1"},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
result := mapGrantsToPermissions(tt.args.requiredPerm, tt.args.grants, tt.args.authConfig)
|
||||
if !EqualStringArray(result, tt.result) {
|
||||
t.Errorf("got wrong result, expecting: %v, actual: %v ", tt.result, result)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_MapRoleToPerm(t *testing.T) {
|
||||
type args struct {
|
||||
requiredPerm string
|
||||
actualRole string
|
||||
authConfig *Config
|
||||
resolvedPermissions []string
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
result []string
|
||||
}{
|
||||
{
|
||||
name: "first perm without context id",
|
||||
args: args{
|
||||
requiredPerm: "project.read",
|
||||
actualRole: "ORG_OWNER",
|
||||
authConfig: &Config{
|
||||
RolePermissionMappings: []RoleMapping{
|
||||
RoleMapping{
|
||||
Role: "IAM_OWNER",
|
||||
Permissions: []string{"project.read"},
|
||||
},
|
||||
RoleMapping{
|
||||
Role: "ORG_OWNER",
|
||||
Permissions: []string{"org.read", "project.read"},
|
||||
},
|
||||
},
|
||||
},
|
||||
resolvedPermissions: []string{},
|
||||
},
|
||||
result: []string{"project.read"},
|
||||
},
|
||||
{
|
||||
name: "existing perm without context id",
|
||||
args: args{
|
||||
requiredPerm: "project.read",
|
||||
actualRole: "ORG_OWNER",
|
||||
authConfig: &Config{
|
||||
RolePermissionMappings: []RoleMapping{
|
||||
RoleMapping{
|
||||
Role: "IAM_OWNER",
|
||||
Permissions: []string{"project.read"},
|
||||
},
|
||||
RoleMapping{
|
||||
Role: "ORG_OWNER",
|
||||
Permissions: []string{"org.read", "project.read"},
|
||||
},
|
||||
},
|
||||
},
|
||||
resolvedPermissions: []string{"project.read"},
|
||||
},
|
||||
result: []string{"project.read"},
|
||||
},
|
||||
{
|
||||
name: "first perm with context id",
|
||||
args: args{
|
||||
requiredPerm: "project.read",
|
||||
actualRole: "PROJECT_OWNER:1",
|
||||
authConfig: &Config{
|
||||
RolePermissionMappings: []RoleMapping{
|
||||
RoleMapping{
|
||||
Role: "PROJECT_OWNER",
|
||||
Permissions: []string{"project.read"},
|
||||
},
|
||||
RoleMapping{
|
||||
Role: "ORG_OWNER",
|
||||
Permissions: []string{"org.read", "project.read"},
|
||||
},
|
||||
},
|
||||
},
|
||||
resolvedPermissions: []string{},
|
||||
},
|
||||
result: []string{"project.read:1"},
|
||||
},
|
||||
{
|
||||
name: "perm with context id, existing global",
|
||||
args: args{
|
||||
requiredPerm: "project.read",
|
||||
actualRole: "PROJECT_OWNER:1",
|
||||
authConfig: &Config{
|
||||
RolePermissionMappings: []RoleMapping{
|
||||
RoleMapping{
|
||||
Role: "PROJECT_OWNER",
|
||||
Permissions: []string{"project.read"},
|
||||
},
|
||||
RoleMapping{
|
||||
Role: "ORG_OWNER",
|
||||
Permissions: []string{"org.read", "project.read"},
|
||||
},
|
||||
},
|
||||
},
|
||||
resolvedPermissions: []string{"project.read"},
|
||||
},
|
||||
result: []string{"project.read", "project.read:1"},
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
result := mapRoleToPerm(tt.args.requiredPerm, tt.args.actualRole, tt.args.authConfig, tt.args.resolvedPermissions)
|
||||
if !EqualStringArray(result, tt.result) {
|
||||
t.Errorf("got wrong result, expecting: %v, actual: %v ", tt.result, result)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_AddRoleContextIDToPerm(t *testing.T) {
|
||||
type args struct {
|
||||
perm string
|
||||
ctxID string
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
result string
|
||||
}{
|
||||
{
|
||||
name: "with ctx id",
|
||||
args: args{
|
||||
perm: "perm1",
|
||||
ctxID: "2",
|
||||
},
|
||||
result: "perm1:2",
|
||||
},
|
||||
{
|
||||
name: "with ctx id",
|
||||
args: args{
|
||||
perm: "perm1",
|
||||
ctxID: "",
|
||||
},
|
||||
result: "perm1",
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
result := addRoleContextIDToPerm(tt.args.perm, tt.args.ctxID)
|
||||
if result != tt.result {
|
||||
t.Errorf("got wrong result, expecting: %v, actual: %v ", tt.result, result)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
func Test_ExistisPerm(t *testing.T) {
|
||||
|
||||
type args struct {
|
||||
existing []string
|
||||
perm string
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
result bool
|
||||
}{
|
||||
{
|
||||
name: "not existing perm",
|
||||
args: args{
|
||||
existing: []string{"perm1", "perm2", "perm3"},
|
||||
perm: "perm4",
|
||||
},
|
||||
result: false,
|
||||
},
|
||||
{
|
||||
name: "existing perm",
|
||||
args: args{
|
||||
existing: []string{"perm1", "perm2", "perm3"},
|
||||
perm: "perm2",
|
||||
},
|
||||
result: true,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
result := existsPerm(tt.args.existing, tt.args.perm)
|
||||
if result != tt.result {
|
||||
t.Errorf("got wrong result, expecting: %v, actual: %v ", tt.result, result)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
20
internal/api/auth/token.go
Normal file
20
internal/api/auth/token.go
Normal file
@@ -0,0 +1,20 @@
|
||||
package auth
|
||||
|
||||
import (
|
||||
"context"
|
||||
"strings"
|
||||
|
||||
"github.com/caos/zitadel/internal/errors"
|
||||
)
|
||||
|
||||
const (
|
||||
BearerPrefix = "Bearer "
|
||||
)
|
||||
|
||||
func verifyAccessToken(ctx context.Context, token string, t TokenVerifier) (string, string, string, error) {
|
||||
parts := strings.Split(token, BearerPrefix)
|
||||
if len(parts) != 2 {
|
||||
return "", "", "", errors.ThrowUnauthenticated(nil, "AUTH-7fs1e", "invalid auth header")
|
||||
}
|
||||
return t.VerifyAccessToken(ctx, parts[1])
|
||||
}
|
63
internal/api/auth/token_test.go
Normal file
63
internal/api/auth/token_test.go
Normal file
@@ -0,0 +1,63 @@
|
||||
package auth
|
||||
|
||||
import (
|
||||
"context"
|
||||
"testing"
|
||||
|
||||
"github.com/caos/zitadel/internal/errors"
|
||||
)
|
||||
|
||||
func Test_VerifyAccessToken(t *testing.T) {
|
||||
|
||||
type args struct {
|
||||
ctx context.Context
|
||||
token string
|
||||
verifier *testVerifier
|
||||
}
|
||||
tests := []struct {
|
||||
name string
|
||||
args args
|
||||
wantErr bool
|
||||
}{
|
||||
{
|
||||
name: "no auth header set",
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
token: "",
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "wrong auth header set",
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
token: "Basic sds",
|
||||
},
|
||||
wantErr: true,
|
||||
},
|
||||
{
|
||||
name: "auth header set",
|
||||
args: args{
|
||||
ctx: context.Background(),
|
||||
token: "Bearer AUTH",
|
||||
},
|
||||
wantErr: false,
|
||||
},
|
||||
}
|
||||
for _, tt := range tests {
|
||||
t.Run(tt.name, func(t *testing.T) {
|
||||
_, _, _, err := verifyAccessToken(tt.args.ctx, tt.args.token, tt.args.verifier)
|
||||
if tt.wantErr && err == nil {
|
||||
t.Errorf("got wrong result, should get err: actual: %v ", err)
|
||||
}
|
||||
|
||||
if !tt.wantErr && err != nil {
|
||||
t.Errorf("got wrong result, should not get err: actual: %v ", err)
|
||||
}
|
||||
|
||||
if tt.wantErr && !errors.IsUnauthenticated(err) {
|
||||
t.Errorf("got wrong err: %v ", err)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
Reference in New Issue
Block a user