mirror of
https://github.com/zitadel/zitadel.git
synced 2025-08-11 18:17:35 +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)
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
34
internal/api/grpc/auth_interceptor.go
Normal file
34
internal/api/grpc/auth_interceptor.go
Normal file
@@ -0,0 +1,34 @@
|
||||
package grpc
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"google.golang.org/grpc"
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/status"
|
||||
|
||||
"github.com/caos/zitadel/internal/api/auth"
|
||||
)
|
||||
|
||||
func AuthorizationInterceptor(verifier auth.TokenVerifier, authConfig *auth.Config, authMethods auth.MethodMapping) func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) {
|
||||
return func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) {
|
||||
authOpt, needsToken := authMethods[info.FullMethod]
|
||||
if !needsToken {
|
||||
return handler(ctx, req)
|
||||
}
|
||||
|
||||
authToken := GetAuthorizationHeader(ctx)
|
||||
if authToken == "" {
|
||||
return nil, status.Error(codes.Unauthenticated, "auth header missing")
|
||||
}
|
||||
|
||||
orgID := GetHeader(ctx, ZitadelOrgID)
|
||||
|
||||
ctx, err := auth.CheckUserAuthorization(ctx, req, authToken, orgID, verifier, authConfig, authOpt)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return handler(ctx, req)
|
||||
}
|
||||
}
|
46
internal/api/grpc/caos_errors.go
Normal file
46
internal/api/grpc/caos_errors.go
Normal file
@@ -0,0 +1,46 @@
|
||||
package grpc
|
||||
|
||||
import (
|
||||
"google.golang.org/grpc/codes"
|
||||
"google.golang.org/grpc/status"
|
||||
|
||||
caos_errs "github.com/caos/zitadel/internal/errors"
|
||||
)
|
||||
|
||||
func CaosToGRPCError(err error) error {
|
||||
if err == nil {
|
||||
return nil
|
||||
}
|
||||
code, msg, ok := extract(err)
|
||||
if !ok {
|
||||
return status.Convert(err).Err()
|
||||
}
|
||||
return status.Error(code, msg)
|
||||
}
|
||||
|
||||
func extract(err error) (c codes.Code, msg string, ok bool) {
|
||||
switch caosErr := err.(type) {
|
||||
case *caos_errs.AlreadyExistsError:
|
||||
return codes.AlreadyExists, caosErr.GetMessage(), true
|
||||
case *caos_errs.DeadlineExceededError:
|
||||
return codes.DeadlineExceeded, caosErr.GetMessage(), true
|
||||
case caos_errs.InternalError:
|
||||
return codes.Internal, caosErr.GetMessage(), true
|
||||
case *caos_errs.InvalidArgumentError:
|
||||
return codes.InvalidArgument, caosErr.GetMessage(), true
|
||||
case *caos_errs.NotFoundError:
|
||||
return codes.NotFound, caosErr.GetMessage(), true
|
||||
case *caos_errs.PermissionDeniedError:
|
||||
return codes.PermissionDenied, caosErr.GetMessage(), true
|
||||
case *caos_errs.PreconditionFailedError:
|
||||
return codes.FailedPrecondition, caosErr.GetMessage(), true
|
||||
case *caos_errs.UnauthenticatedError:
|
||||
return codes.Unauthenticated, caosErr.GetMessage(), true
|
||||
case *caos_errs.UnavailableError:
|
||||
return codes.Unavailable, caosErr.GetMessage(), true
|
||||
case *caos_errs.UnimplementedError:
|
||||
return codes.Unimplemented, caosErr.GetMessage(), true
|
||||
default:
|
||||
return codes.Unknown, err.Error(), false
|
||||
}
|
||||
}
|
14
internal/api/grpc/error_interceptor.go
Normal file
14
internal/api/grpc/error_interceptor.go
Normal file
@@ -0,0 +1,14 @@
|
||||
package grpc
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"google.golang.org/grpc"
|
||||
)
|
||||
|
||||
func ErrorHandler() func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) {
|
||||
return func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) {
|
||||
resp, err := handler(ctx, req)
|
||||
return resp, CaosToGRPCError(err)
|
||||
}
|
||||
}
|
21
internal/api/grpc/header.go
Normal file
21
internal/api/grpc/header.go
Normal file
@@ -0,0 +1,21 @@
|
||||
package grpc
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/grpc-ecosystem/go-grpc-middleware/util/metautils"
|
||||
)
|
||||
|
||||
const (
|
||||
Authorization = "authorization"
|
||||
|
||||
ZitadelOrgID = "x-zitadel-orgid"
|
||||
)
|
||||
|
||||
func GetHeader(ctx context.Context, headername string) string {
|
||||
return metautils.ExtractIncoming(ctx).Get(headername)
|
||||
}
|
||||
|
||||
func GetAuthorizationHeader(ctx context.Context) string {
|
||||
return GetHeader(ctx, Authorization)
|
||||
}
|
53
internal/api/grpc/probes.go
Normal file
53
internal/api/grpc/probes.go
Normal file
@@ -0,0 +1,53 @@
|
||||
package grpc
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/caos/logging"
|
||||
"github.com/golang/protobuf/ptypes/empty"
|
||||
structpb "github.com/golang/protobuf/ptypes/struct"
|
||||
|
||||
"github.com/caos/zitadel/internal/errors"
|
||||
"github.com/caos/zitadel/internal/proto"
|
||||
)
|
||||
|
||||
type ValidationFunction func(ctx context.Context) error
|
||||
|
||||
type Validator struct {
|
||||
validations map[string]ValidationFunction
|
||||
}
|
||||
|
||||
func NewValidator(validations map[string]ValidationFunction) *Validator {
|
||||
return &Validator{validations: validations}
|
||||
}
|
||||
|
||||
func (v *Validator) Healthz(_ context.Context, e *empty.Empty) (*empty.Empty, error) {
|
||||
return e, nil
|
||||
}
|
||||
|
||||
func (v *Validator) Ready(ctx context.Context, e *empty.Empty) (*empty.Empty, error) {
|
||||
return e, ready(ctx, v.validations)
|
||||
}
|
||||
|
||||
func (v *Validator) Validate(ctx context.Context, _ *empty.Empty) (*structpb.Struct, error) {
|
||||
validations := validate(ctx, v.validations)
|
||||
return proto.ToPBStruct(validations)
|
||||
}
|
||||
|
||||
func ready(ctx context.Context, validations map[string]ValidationFunction) error {
|
||||
if len(validate(ctx, validations)) == 0 {
|
||||
return nil
|
||||
}
|
||||
return errors.ThrowInternal(nil, "API-2jD9a", "not ready")
|
||||
}
|
||||
|
||||
func validate(ctx context.Context, validations map[string]ValidationFunction) map[string]error {
|
||||
errors := make(map[string]error)
|
||||
for id, validation := range validations {
|
||||
if err := validation(ctx); err != nil {
|
||||
logging.Log("API-vf823").WithError(err).Error("validation failed")
|
||||
errors[id] = err
|
||||
}
|
||||
}
|
||||
return errors
|
||||
}
|
125
internal/api/http/cookie.go
Normal file
125
internal/api/http/cookie.go
Normal file
@@ -0,0 +1,125 @@
|
||||
package http
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/gorilla/securecookie"
|
||||
|
||||
"github.com/caos/zitadel/internal/errors"
|
||||
)
|
||||
|
||||
type CookieHandler struct {
|
||||
securecookie *securecookie.SecureCookie
|
||||
secureOnly bool
|
||||
sameSite http.SameSite
|
||||
path string
|
||||
maxAge int
|
||||
domain string
|
||||
}
|
||||
|
||||
func NewCookieHandler(opts ...CookieHandlerOpt) *CookieHandler {
|
||||
c := &CookieHandler{
|
||||
secureOnly: true,
|
||||
sameSite: http.SameSiteLaxMode,
|
||||
path: "/",
|
||||
}
|
||||
|
||||
for _, opt := range opts {
|
||||
opt(c)
|
||||
}
|
||||
return c
|
||||
}
|
||||
|
||||
type CookieHandlerOpt func(*CookieHandler)
|
||||
|
||||
func WithEncryption(hashKey, encryptKey []byte) CookieHandlerOpt {
|
||||
return func(c *CookieHandler) {
|
||||
c.securecookie = securecookie.New(hashKey, encryptKey)
|
||||
}
|
||||
}
|
||||
|
||||
func WithUnsecure() CookieHandlerOpt {
|
||||
return func(c *CookieHandler) {
|
||||
c.secureOnly = false
|
||||
}
|
||||
}
|
||||
|
||||
func WithSameSite(sameSite http.SameSite) CookieHandlerOpt {
|
||||
return func(c *CookieHandler) {
|
||||
c.sameSite = sameSite
|
||||
}
|
||||
}
|
||||
|
||||
func WithPath(path string) CookieHandlerOpt {
|
||||
return func(c *CookieHandler) {
|
||||
c.path = path
|
||||
}
|
||||
}
|
||||
|
||||
func WithMaxAge(maxAge int) CookieHandlerOpt {
|
||||
return func(c *CookieHandler) {
|
||||
c.maxAge = maxAge
|
||||
c.securecookie.MaxAge(maxAge)
|
||||
}
|
||||
}
|
||||
|
||||
func WithDomain(domain string) CookieHandlerOpt {
|
||||
return func(c *CookieHandler) {
|
||||
c.domain = domain
|
||||
}
|
||||
}
|
||||
|
||||
func (c *CookieHandler) GetCookieValue(r *http.Request, name string) (string, error) {
|
||||
cookie, err := r.Cookie(name)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return cookie.Value, nil
|
||||
}
|
||||
|
||||
func (c *CookieHandler) GetEncryptedCookieValue(r *http.Request, name string, value interface{}) error {
|
||||
cookie, err := r.Cookie(name)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if c.securecookie == nil {
|
||||
return errors.ThrowInternal(nil, "HTTP-X6XpnL", "securecookie not configured")
|
||||
}
|
||||
if err := c.securecookie.Decode(name, cookie.Value, value); err != nil {
|
||||
return err
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *CookieHandler) SetCookie(w http.ResponseWriter, name string, value string) {
|
||||
c.httpSet(w, name, value, c.maxAge)
|
||||
}
|
||||
|
||||
func (c *CookieHandler) SetEncryptedCookie(w http.ResponseWriter, name string, value interface{}) error {
|
||||
if c.securecookie == nil {
|
||||
return errors.ThrowInternal(nil, "HTTP-s2HUtx", "securecookie not configured")
|
||||
}
|
||||
encoded, err := c.securecookie.Encode(name, value)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
c.httpSet(w, name, encoded, c.maxAge)
|
||||
return nil
|
||||
}
|
||||
|
||||
func (c *CookieHandler) DeleteCookie(w http.ResponseWriter, name string) {
|
||||
c.httpSet(w, name, "", -1)
|
||||
}
|
||||
|
||||
func (c *CookieHandler) httpSet(w http.ResponseWriter, name, value string, maxage int) {
|
||||
http.SetCookie(w, &http.Cookie{
|
||||
Name: name,
|
||||
Value: value,
|
||||
Domain: c.domain,
|
||||
Path: c.path,
|
||||
MaxAge: maxage,
|
||||
HttpOnly: true,
|
||||
Secure: c.secureOnly,
|
||||
SameSite: c.sameSite,
|
||||
})
|
||||
}
|
28
internal/api/http/parser.go
Normal file
28
internal/api/http/parser.go
Normal file
@@ -0,0 +1,28 @@
|
||||
package http
|
||||
|
||||
import (
|
||||
"net/http"
|
||||
|
||||
"github.com/gorilla/schema"
|
||||
|
||||
"github.com/caos/zitadel/internal/errors"
|
||||
)
|
||||
|
||||
type Parser struct {
|
||||
decoder *schema.Decoder
|
||||
}
|
||||
|
||||
func NewParser() *Parser {
|
||||
d := schema.NewDecoder()
|
||||
d.IgnoreUnknownKeys(true)
|
||||
return &Parser{d}
|
||||
}
|
||||
|
||||
func (p *Parser) Parse(r *http.Request, data interface{}) error {
|
||||
err := r.ParseForm()
|
||||
if err != nil {
|
||||
return errors.ThrowInternal(err, "FORM-lCC9zI", "error parsing http form")
|
||||
}
|
||||
|
||||
return p.decoder.Decode(data, r.Form)
|
||||
}
|
52
internal/proto/struct.go
Normal file
52
internal/proto/struct.go
Normal file
@@ -0,0 +1,52 @@
|
||||
package proto
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
|
||||
"github.com/golang/protobuf/jsonpb"
|
||||
pb_struct "github.com/golang/protobuf/ptypes/struct"
|
||||
|
||||
"github.com/caos/logging"
|
||||
)
|
||||
|
||||
func MustToPBStruct(object interface{}) *pb_struct.Struct {
|
||||
s, err := ToPBStruct(object)
|
||||
logging.Log("PROTO-7Aa3t").OnError(err).Panic("unable to map object to pb-struct")
|
||||
|
||||
return s
|
||||
}
|
||||
|
||||
func BytesToPBStruct(b []byte) (*pb_struct.Struct, error) {
|
||||
fields := new(pb_struct.Struct)
|
||||
err := jsonpb.Unmarshal(bytes.NewReader(b), fields)
|
||||
return fields, err
|
||||
}
|
||||
|
||||
func ToPBStruct(object interface{}) (*pb_struct.Struct, error) {
|
||||
fields := new(pb_struct.Struct)
|
||||
|
||||
marshalled, err := json.Marshal(object)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
err = jsonpb.Unmarshal(bytes.NewReader(marshalled), fields)
|
||||
|
||||
return fields, err
|
||||
}
|
||||
|
||||
func MustFromPBStruct(object interface{}, s *pb_struct.Struct) {
|
||||
err := FromPBStruct(object, s)
|
||||
logging.Log("PROTO-WeMYY").OnError(err).Panic("unable to map pb-struct into object")
|
||||
}
|
||||
|
||||
func FromPBStruct(object interface{}, s *pb_struct.Struct) error {
|
||||
marshaller := new(jsonpb.Marshaler)
|
||||
jsonString, err := marshaller.MarshalToString(s)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
return json.Unmarshal([]byte(jsonString), object)
|
||||
}
|
53
internal/proto/struct_test.go
Normal file
53
internal/proto/struct_test.go
Normal file
@@ -0,0 +1,53 @@
|
||||
package proto
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
pb_struct "github.com/golang/protobuf/ptypes/struct"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
"github.com/stretchr/testify/require"
|
||||
)
|
||||
|
||||
func TestToPBStruct(t *testing.T) {
|
||||
obj := struct {
|
||||
ID string
|
||||
Name string
|
||||
Seq uint64
|
||||
}{
|
||||
ID: "asdf",
|
||||
Name: "ueli",
|
||||
Seq: 208582075,
|
||||
}
|
||||
fields, err := ToPBStruct(&obj)
|
||||
require.NoError(t, err)
|
||||
require.Len(t, fields.Fields, 3)
|
||||
|
||||
assert.Equal(t, obj.ID, fields.Fields["ID"].GetStringValue())
|
||||
assert.Equal(t, int(obj.Seq), int(fields.Fields["Seq"].GetNumberValue()))
|
||||
assert.Equal(t, obj.Name, fields.Fields["Name"].GetStringValue())
|
||||
}
|
||||
|
||||
func TestFromPBStruct(t *testing.T) {
|
||||
name := "ueli"
|
||||
id := "asdf"
|
||||
seq := float64(208582075)
|
||||
s := &pb_struct.Struct{Fields: map[string]*pb_struct.Value{
|
||||
"ID": &pb_struct.Value{Kind: &pb_struct.Value_StringValue{StringValue: id}},
|
||||
"Name": &pb_struct.Value{Kind: &pb_struct.Value_StringValue{StringValue: name}},
|
||||
"Seq": &pb_struct.Value{Kind: &pb_struct.Value_NumberValue{NumberValue: seq}},
|
||||
}}
|
||||
|
||||
obj := struct {
|
||||
ID string
|
||||
Name string
|
||||
Seq uint64
|
||||
}{}
|
||||
|
||||
err := FromPBStruct(&obj, s)
|
||||
require.NoError(t, err)
|
||||
|
||||
assert.Equal(t, id, obj.ID)
|
||||
assert.Equal(t, name, obj.Name)
|
||||
assert.Equal(t, int(seq), int(obj.Seq))
|
||||
}
|
Reference in New Issue
Block a user