Skip to content
Snippets Groups Projects
Commit 678af3c5 authored by Anton Sattarov's avatar Anton Sattarov :cucumber:
Browse files

Merge branch 'task/PRXS-1070-Auth' into 'feature/1004-AddPublicEntities'

Перенос 'auth'

See merge request perxis/perxis-go!30
parents a47f54b0 17f1ebaf
No related branches found
No related tags found
No related merge requests found
package auth
import (
"context"
"fmt"
"git.perx.ru/perxis/perxis-go/pkg/environments"
"git.perx.ru/perxis/perxis-go/pkg/errors"
"git.perx.ru/perxis/perxis-go/pkg/members"
"git.perx.ru/perxis/perxis-go/pkg/permission"
"git.perx.ru/perxis/perxis-go/pkg/roles"
"git.perx.ru/perxis/perxis-go/pkg/spaces"
)
type Anonymous struct {
roles roles.Roles
spaces spaces.Spaces
spaceID string
environments environments.Environments
}
func (Anonymous) GetID(ctx context.Context) string { return "anonymous" }
func (Anonymous) IsValid(ctx context.Context) bool { return false }
func (Anonymous) IsSystem(ctx context.Context) bool { return false }
func (Anonymous) IsManagementAllowed(ctx context.Context, spaceID string) error {
return ErrAccessDenied
}
func (a Anonymous) Space(spaceID string) SpaceAccessor {
a.spaceID = spaceID
return &a
}
func (a *Anonymous) getSpace(ctx context.Context, spaceID string) *spaces.Space {
if spaceID == "" {
return nil
}
space, _ := a.spaces.Get(WithSystem(ctx), spaceID)
return space
}
func (a *Anonymous) HasSpaceAccess(ctx context.Context, spaceID string) bool {
if a.spaceID == "" || a.spaces == nil {
return false
}
return a.Role(ctx, spaceID) != nil
}
func (a *Anonymous) Member(ctx context.Context) members.Role {
return members.NotMember
}
func (a *Anonymous) Role(ctx context.Context, spaceID string) *roles.Role {
if a.spaceID == "" || a.roles == nil {
return nil
}
role, err := a.roles.Get(WithSystem(ctx), spaceID, roles.AnonymousRole)
if err != nil {
return nil
}
return role
}
func (a *Anonymous) Rules(ctx context.Context, spaceID, envID string) permission.Ruleset {
role := a.Role(WithSystem(ctx), spaceID)
if role == nil {
return nil
}
if !a.HasEnvironmentAccess(ctx, spaceID, envID) {
return nil
}
return role.Rules
}
func (a *Anonymous) HasEnvironmentAccess(ctx context.Context, space, env string) bool {
return hasEnvironmentAccess(ctx, a.environments, a.Role(ctx, space), env)
}
func (Anonymous) Format(f fmt.State, verb rune) {
f.Write([]byte("AnonymousPrincipal{}"))
}
func (a Anonymous) HasAccess(ctx context.Context, spaceID, orgID string) error {
if !a.IsValid(ctx) {
return ErrAccessDenied
}
if a.IsSystem(ctx) {
return nil
}
if spaceID != "" {
hasAllow, err := a.hasRole(ctx, spaceID)
if err != nil {
return err
}
if hasAllow {
return nil
}
}
if a.Member(ctx).IsPrivileged() {
return nil
}
return ErrAccessDenied
}
func (a *Anonymous) hasRole(ctx context.Context, spaceID string) (bool, error) {
if a.spaceID == "" || a.roles == nil {
return false, nil
}
_, err := a.roles.Get(WithSystem(ctx), spaceID, roles.AnonymousRole)
if err == nil {
return true, nil
}
if errors.Is(err, ErrNotFound) {
if sp := a.getSpace(ctx, spaceID); sp == nil {
return false, ErrNotFound
}
}
return false, nil
}
package auth
import (
"context"
"fmt"
"git.perx.ru/perxis/perxis-go/pkg/clients"
"git.perx.ru/perxis/perxis-go/pkg/collaborators"
"git.perx.ru/perxis/perxis-go/pkg/environments"
"git.perx.ru/perxis/perxis-go/pkg/errors"
"git.perx.ru/perxis/perxis-go/pkg/members"
"git.perx.ru/perxis/perxis-go/pkg/permission"
"git.perx.ru/perxis/perxis-go/pkg/roles"
"git.perx.ru/perxis/perxis-go/pkg/spaces"
)
type ClientPrincipal struct {
identity *clients.GetByParams
spaceID string
space *spaces.Space
client *clients.Client
invalid bool
spaces spaces.Spaces
environments environments.Environments
clients clients.Clients
roles roles.Roles
collaborators collaborators.Collaborators
}
func NewClientPrincipal(identity *clients.GetByParams) *ClientPrincipal {
return &ClientPrincipal{identity: identity}
}
func (c ClientPrincipal) Format(f fmt.State, verb rune) {
var identity string
switch {
case c.identity == nil:
identity = "<nil>"
case c.identity.APIKey != "":
identity = fmt.Sprintf("APIKey: '%s'", c.identity.APIKey)
case c.identity.OAuthClientID != "":
identity = fmt.Sprintf("OAuthClientID: '%s'", c.identity.OAuthClientID)
case c.identity.TLSSubject != "":
identity = fmt.Sprintf("TLSSubject: '%s'", c.identity.TLSSubject)
}
var id string
if c.client != nil {
id = c.client.ID
}
f.Write([]byte(fmt.Sprintf("ClientPrincipal{ID: '%s', Identity: {%s}}", id, identity)))
}
func (c *ClientPrincipal) Space(spaceID string) SpaceAccessor {
c.spaceID = spaceID
c.space = nil
c.invalid = false
c.client = nil
return c
}
func (c *ClientPrincipal) getSpace(ctx context.Context, spaceID string) *spaces.Space {
if spaceID == "" {
return nil
}
space, _ := c.spaces.Get(WithSystem(ctx), spaceID)
return space
}
func (ClientPrincipal) IsSystem(ctx context.Context) bool {
return false
}
func (c *ClientPrincipal) IsManagementAllowed(ctx context.Context, spaceID string) error {
if !c.IsValid(ctx) {
return ErrAccessDenied
}
if role := c.Role(ctx, spaceID); role != nil && role.AllowManagement {
return nil
}
return ErrAccessDenied
}
func (c *ClientPrincipal) Member(ctx context.Context) members.Role {
return members.NotMember
}
func (c *ClientPrincipal) HasSpaceAccess(ctx context.Context, spaceID string) bool {
if c.spaceID == "" {
return false
}
client, _ := c.Client(ctx)
return client != nil && client.SpaceID == spaceID
}
func (c *ClientPrincipal) GetID(ctx context.Context) string {
client, _ := c.Client(ctx)
if client == nil {
return ""
}
return client.ID
}
func (c *ClientPrincipal) GetIdentity(ctx context.Context) *clients.GetByParams {
return c.identity
}
func (c *ClientPrincipal) IsValid(ctx context.Context) bool {
if c == nil {
return false
}
client, _ := c.Client(ctx)
return client != nil
}
func (c *ClientPrincipal) Client(ctx context.Context) (*clients.Client, error) {
if c.invalid {
return nil, nil
}
if c.client != nil {
return c.client, nil
}
if c.clients == nil {
c.invalid = true
return nil, nil
}
client, err := c.clients.GetBy(WithSystem(ctx), c.spaceID, c.identity)
if err != nil || client == nil || client.IsDisabled() {
c.invalid = true
return nil, err
}
c.client = client
return c.client, nil
}
func (c *ClientPrincipal) HasEnvironmentAccess(ctx context.Context, spaceID, envID string) bool {
return hasEnvironmentAccess(ctx, c.environments, c.Role(ctx, spaceID), envID)
}
func (c *ClientPrincipal) getRoleID(ctx context.Context, spaceID string) (string, bool) {
if c.spaceID == "" || spaceID == "" {
return "", false
}
if spaceID == c.spaceID {
cl, _ := c.Client(ctx)
if cl == nil || cl.RoleID == "" {
return "", false
}
return cl.RoleID, true
}
rID, err := c.collaborators.Get(WithSystem(ctx), spaceID, c.spaceID)
if err != nil {
rID = roles.ViewRole
}
return rID, true
}
func (c *ClientPrincipal) Role(ctx context.Context, spaceID string) *roles.Role {
if c.spaceID == "" {
return nil
}
rID, ok := c.getRoleID(ctx, spaceID)
if !ok {
return nil
}
role, err := c.roles.Get(WithSystem(ctx), spaceID, rID)
if err == nil {
//c.hasRole = true
//c.role = role
return role
}
return nil
}
func (c *ClientPrincipal) Rules(ctx context.Context, spaceID, envID string) permission.Ruleset {
if c.spaceID == "" || spaceID == "" || envID == "" {
return nil
}
role := c.Role(ctx, spaceID)
if role == nil {
return nil
}
if role.AllowManagement {
return permission.PrivilegedRuleset{}
}
if hasEnvironmentAccess(ctx, c.environments, role, envID) {
return role.Rules
}
return nil
}
func (c *ClientPrincipal) HasAccess(ctx context.Context, spaceID, orgID string) error {
if !c.IsValid(ctx) {
return ErrAccessDenied
}
if c.IsSystem(ctx) {
return nil
}
if spaceID != "" {
if c.spaceID == "" {
return ErrAccessDenied
}
client, _ := c.Client(ctx)
if client != nil && client.SpaceID == spaceID {
return nil
}
}
if c.Member(ctx).IsPrivileged() {
return nil
}
return ErrAccessDenied
}
func (c *ClientPrincipal) hasRole(ctx context.Context, spaceID string) (bool, error) {
if c.spaceID == "" {
return false, nil
}
client, err := c.Client(ctx)
if err != nil && errors.Is(err, ErrNotFound) {
if sp := c.getSpace(ctx, spaceID); sp == nil {
return false, ErrNotFound
}
}
if client != nil && client.SpaceID == spaceID {
return true, nil
}
return false, nil
}
package auth
import (
"context"
)
type principalKey struct{}
func GetPrincipal(ctx context.Context) Principal {
p, _ := ctx.Value(principalKey{}).(Principal)
if p == nil {
return Anonymous{}
}
return p
}
func WithPrincipal(ctx context.Context, p Principal) context.Context {
if ctx == nil {
ctx = context.Background()
}
return context.WithValue(ctx, principalKey{}, p)
}
func WithSystem(ctx context.Context) context.Context {
return WithPrincipal(ctx, &SystemPrincipal{})
}
package auth
import (
"git.perx.ru/perxis/perxis-go/pkg/errors"
)
var (
ErrAccessDenied = errors.PermissionDenied(errors.New("access denied"))
ErrNotFound = errors.NotFound(errors.New("not found"))
)
package auth
import (
"strings"
"git.perx.ru/perxis/perxis-go/pkg/clients"
"git.perx.ru/perxis/perxis-go/pkg/collaborators"
"git.perx.ru/perxis/perxis-go/pkg/environments"
"git.perx.ru/perxis/perxis-go/pkg/members"
"git.perx.ru/perxis/perxis-go/pkg/roles"
"git.perx.ru/perxis/perxis-go/pkg/spaces"
"git.perx.ru/perxis/perxis-go/pkg/users"
)
type PrincipalFactory struct {
users.Users
members.Members
collaborators.Collaborators
roles.Roles
clients.Clients
spaces.Spaces
environments.Environments
}
func (f PrincipalFactory) User(identity string) Principal {
return &UserPrincipal{
identity: identity,
users: f.Users,
members: f.Members,
roles: f.Roles,
collaborators: f.Collaborators,
spaces: f.Spaces,
environments: f.Environments,
}
}
func (f PrincipalFactory) Client(param *clients.GetByParams) Principal {
return &ClientPrincipal{
identity: param,
//authID: authID,
clients: f.Clients,
environments: f.Environments,
roles: f.Roles,
spaces: f.Spaces,
collaborators: f.Collaborators,
}
}
func (f PrincipalFactory) Anonymous() Principal {
return &Anonymous{
roles: f.Roles,
spaces: f.Spaces,
}
}
func (f PrincipalFactory) System() Principal {
return &SystemPrincipal{}
}
func (f PrincipalFactory) Principal(principalId string) Principal {
switch {
case strings.Contains(principalId, "Subject="):
return f.Client(&clients.GetByParams{TLSSubject: getSubject(principalId)})
case strings.HasSuffix(principalId, "@clients"):
return f.Client(&clients.GetByParams{OAuthClientID: strings.TrimSuffix(principalId, "@clients")})
case strings.HasPrefix(principalId, "API-Key"):
return f.Client(&clients.GetByParams{APIKey: strings.TrimPrefix(principalId, "API-Key ")})
default:
return f.User(principalId)
}
}
func getSubject(header string) string {
var p string
for _, part := range strings.Split(header, ";") {
if strings.Contains(part, "Subject") {
p = strings.TrimSuffix(strings.TrimPrefix(part, "Subject=\""), "\"")
break
}
}
return p
}
package auth
import (
"context"
kitgrpc "github.com/go-kit/kit/transport/grpc"
"google.golang.org/grpc"
"google.golang.org/grpc/metadata"
)
const (
OAuth2IdentityMetadata = "x-perxis-identity"
TLSIdentityMetadata = "x-forwarded-client-cert"
AccessMetadata = "x-perxis-access"
AuthorizationMetadata = "authorization"
)
func GRPCToContext(factory *PrincipalFactory) kitgrpc.ServerRequestFunc {
return func(ctx context.Context, md metadata.MD) context.Context {
if identity := md.Get(TLSIdentityMetadata); len(identity) > 0 {
return WithPrincipal(ctx, factory.Principal(identity[0]))
}
if identity := md.Get(OAuth2IdentityMetadata); len(identity) > 0 {
return WithPrincipal(ctx, factory.Principal(identity[0]))
}
if identity := md.Get(AuthorizationMetadata); len(identity) > 0 {
return WithPrincipal(ctx, factory.Principal(identity[0]))
}
if access := md.Get(AccessMetadata); len(access) > 0 {
return WithPrincipal(ctx, factory.System())
}
return WithPrincipal(ctx, factory.Anonymous())
}
}
func ContextToGRPC() kitgrpc.ClientRequestFunc {
return func(ctx context.Context, md *metadata.MD) context.Context {
p := GetPrincipal(ctx)
switch p := p.(type) {
case *UserPrincipal:
if p.GetIdentity(ctx) != "" {
(*md)[OAuth2IdentityMetadata] = []string{p.GetIdentity(ctx)}
}
case *ClientPrincipal:
if ident := p.GetIdentity(ctx); ident != nil {
switch {
case ident.OAuthClientID != "":
(*md)[OAuth2IdentityMetadata] = []string{ident.OAuthClientID + "@clients"}
case ident.TLSSubject != "":
(*md)[TLSIdentityMetadata] = []string{ident.TLSSubject}
case ident.APIKey != "":
(*md)[AuthorizationMetadata] = []string{"API-Key " + ident.APIKey}
}
}
case *SystemPrincipal:
(*md)[AccessMetadata] = []string{p.GetID(ctx)}
}
return ctx
}
}
// PrincipalServerInterceptor - grpc-интерсептор, который используется для получения данных принципала из grpc-метаданы и добавления в контекст ''. В случае, если
// сервис не использует проверку прав 'Principal' к системе, в параметрах передается пустой объект '&PrincipalFactory{}'
func PrincipalServerInterceptor(factory *PrincipalFactory) grpc.UnaryServerInterceptor {
return func(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) {
if md, ok := metadata.FromIncomingContext(ctx); ok {
ctx = GRPCToContext(factory)(ctx, md)
}
return handler(ctx, req)
}
}
// PrincipalClientInterceptor - grpc-интерсептор, который используется для получения данных принципала. В случае, если
// сервис не использует проверку прав 'Principal' к системе, в параметрах передается пустой объект '&PrincipalFactory{}'
func PrincipalClientInterceptor() grpc.UnaryClientInterceptor {
return func(ctx context.Context, method string, req, reply interface{}, cc *grpc.ClientConn, invoker grpc.UnaryInvoker, opts ...grpc.CallOption) error {
md, ok := metadata.FromOutgoingContext(ctx)
if !ok {
md = metadata.MD{}
}
ctx = metadata.NewOutgoingContext(ContextToGRPC()(ctx, &md), md)
return invoker(ctx, method, req, reply, cc, opts...)
}
}
package auth
import (
"context"
"git.perx.ru/perxis/perxis-go/pkg/environments"
"git.perx.ru/perxis/perxis-go/pkg/members"
"git.perx.ru/perxis/perxis-go/pkg/permission"
"git.perx.ru/perxis/perxis-go/pkg/roles"
"git.perx.ru/perxis/perxis/util"
)
type Principal interface {
GetID(ctx context.Context) string
IsValid(ctx context.Context) bool
IsSystem(ctx context.Context) bool
HasAccess(ctx context.Context, spID, orgID string) error
IsManagementAllowed(ctx context.Context, spaceID string) error
}
type SpaceAccessor interface {
Principal
Space(spaceID string) SpaceAccessor
// HasSpaceAccess проверяет, есть ли у принципала доступ на чтение пространства
// (просмотр информации о пространстве, окружений, т.д. - доступ к записям коллекций
// определяется отдельным набором правил, см. SpaceAccessor.Rules())
HasSpaceAccess(ctx context.Context, spaceID string) bool
HasEnvironmentAccess(ctx context.Context, spaceID, env string) bool
// Member возвращает роль принципала в организации
Member(ctx context.Context) members.Role
Role(ctx context.Context, spaceID string) *roles.Role
// Rules возвращает набор правил, по которым принципал может получить
// доступ к записям коллекций пространства.
Rules(ctx context.Context, spaceID, envID string) permission.Ruleset
}
type OrganizationAccessor interface {
Principal
Organization(orgID string) OrganizationAccessor
Member(ctx context.Context) members.Role
}
func hasEnvironmentAccess(ctx context.Context, envsrv environments.Environments, role *roles.Role, envID string) bool {
if role == nil || role.SpaceID == "" || envID == "" {
return false
}
if role.AllowManagement {
return true
}
envs := role.Environments
// Если явно не указаны доступные окружения - доступ по умолчанию к окружению master
if len(envs) == 0 {
envs = []string{environments.DefaultEnvironment}
}
for _, ce := range envs {
if envID == ce || util.GlobMatch(envID, ce) {
return true
}
}
e, err := envsrv.Get(WithSystem(ctx), role.SpaceID, envID)
if err != nil || e == nil {
return false
}
aliases := append(e.Aliases, e.ID)
for _, ce := range envs {
for _, al := range aliases {
if al == ce || util.GlobMatch(al, ce) {
return true
}
}
}
return false
}
package auth
import (
"context"
"testing"
"git.perx.ru/perxis/perxis-go/pkg/environments"
mocksenvs "git.perx.ru/perxis/perxis-go/pkg/environments/mocks"
"git.perx.ru/perxis/perxis-go/pkg/roles"
"github.com/stretchr/testify/mock"
)
func Test_hasEnvironmentAccess(t *testing.T) {
type args struct {
ctx context.Context
envscall func(envsservice *mocksenvs.Environments)
role *roles.Role
envID string
}
tests := []struct {
name string
args args
want bool
}{
{
name: "simple",
args: args{
ctx: context.Background(),
role: &roles.Role{
ID: "1",
SpaceID: "space",
Description: "Current",
Environments: []string{"env1", "env2"},
},
envID: "env1",
},
want: true,
},
{
name: "glob env in role test: e*",
args: args{
ctx: context.Background(),
envscall: func(envsservice *mocksenvs.Environments) {
envsservice.On("Get", mock.Anything, mock.Anything, mock.Anything).Return(&environments.Environment{
ID: "env1",
SpaceID: "space",
Aliases: []string{"master"},
}, nil).Once()
},
role: &roles.Role{
ID: "1",
SpaceID: "space",
Description: "Current",
Environments: []string{"e*"},
},
envID: "env",
},
want: true,
},
{
name: "glob env in role test: *n*",
args: args{
ctx: context.Background(),
envscall: func(envsservice *mocksenvs.Environments) {
envsservice.On("Get", mock.Anything, mock.Anything, mock.Anything).Return(&environments.Environment{
ID: "env1",
SpaceID: "space",
Aliases: []string{"master"},
}, nil).Once()
},
role: &roles.Role{
ID: "1",
SpaceID: "space",
Description: "Current",
Environments: []string{"*n*"},
},
envID: "env",
},
want: true,
},
{
name: "glob env in role test: *1",
args: args{
ctx: context.Background(),
envscall: func(envsservice *mocksenvs.Environments) {
envsservice.On("Get", mock.Anything, mock.Anything, mock.Anything).Return(&environments.Environment{
ID: "env1",
SpaceID: "space",
Aliases: []string{"master"},
}, nil).Once()
},
role: &roles.Role{
ID: "1",
SpaceID: "space",
Description: "Current",
Environments: []string{"*1"},
},
envID: "env",
},
want: true,
},
{
name: "glob env in role test (alias): ma*",
args: args{
ctx: context.Background(),
envscall: func(envsservice *mocksenvs.Environments) {
envsservice.On("Get", mock.Anything, mock.Anything, mock.Anything).Return(&environments.Environment{
ID: "env1",
SpaceID: "space",
Aliases: []string{"master"},
}, nil).Once()
},
role: &roles.Role{
ID: "1",
SpaceID: "space",
Description: "Current",
Environments: []string{"ma*"},
},
envID: "env1",
},
want: true,
},
{
name: "glob env in role test: *",
args: args{
ctx: context.Background(),
envscall: func(envsservice *mocksenvs.Environments) {
envsservice.On("Get", mock.Anything, mock.Anything, mock.Anything).Return(&environments.Environment{
ID: "env1",
SpaceID: "space",
Aliases: []string{"master"},
}, nil).Once()
},
role: &roles.Role{
ID: "1",
SpaceID: "space",
Description: "Current",
Environments: []string{"*"},
},
envID: "env1",
},
want: true,
},
{
name: "glob env in role test: q*",
args: args{
ctx: context.Background(),
envscall: func(envsservice *mocksenvs.Environments) {
envsservice.On("Get", mock.Anything, mock.Anything, mock.Anything).Return(&environments.Environment{
ID: "env1",
SpaceID: "space",
Aliases: []string{"master"},
}, nil).Once()
},
role: &roles.Role{
ID: "1",
SpaceID: "space",
Description: "Current",
Environments: []string{"q*"},
},
envID: "env1",
},
want: false,
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
envsservice := &mocksenvs.Environments{}
if tt.args.envscall != nil {
tt.args.envscall(envsservice)
}
if got := hasEnvironmentAccess(tt.args.ctx, envsservice, tt.args.role, tt.args.envID); got != tt.want {
t.Errorf("hasEnvironmentAccess() = %v, want %v", got, tt.want)
}
})
}
}
package auth
import (
"context"
"fmt"
"git.perx.ru/perxis/perxis-go/pkg/members"
"git.perx.ru/perxis/perxis-go/pkg/permission"
"git.perx.ru/perxis/perxis-go/pkg/roles"
)
type SystemPrincipal struct{}
const (
SystemID = "system"
)
func (p SystemPrincipal) GetID(ctx context.Context) string { return SystemID }
func (SystemPrincipal) IsValid(ctx context.Context) bool { return true }
func (SystemPrincipal) IsSystem(ctx context.Context) bool { return true }
func (SystemPrincipal) IsManagementAllowed(ctx context.Context, spaceID string) error { return nil }
func (p SystemPrincipal) Organization(_ string) OrganizationAccessor { return p }
func (p SystemPrincipal) Space(_ string) SpaceAccessor { return p }
func (SystemPrincipal) HasSpaceAccess(_ context.Context, _ string) bool { return true }
func (SystemPrincipal) HasAccess(ctx context.Context, spaceID, orgID string) error {
return nil
}
func (SystemPrincipal) HasEnvironmentAccess(_ context.Context, _, _ string) bool { return true }
func (SystemPrincipal) Member(_ context.Context) members.Role { return members.NotMember }
func (SystemPrincipal) Role(_ context.Context, _ string) *roles.Role { return nil }
func (SystemPrincipal) Rules(_ context.Context, _, _ string) permission.Ruleset {
return &permission.PrivilegedRuleset{}
}
func (SystemPrincipal) Format(f fmt.State, verb rune) {
f.Write([]byte("SystemPrincipal{}"))
}
package auth
import (
"context"
"fmt"
"git.perx.ru/perxis/perxis-go/pkg/collaborators"
"git.perx.ru/perxis/perxis-go/pkg/environments"
"git.perx.ru/perxis/perxis-go/pkg/errors"
"git.perx.ru/perxis/perxis-go/pkg/members"
"git.perx.ru/perxis/perxis-go/pkg/permission"
"git.perx.ru/perxis/perxis-go/pkg/roles"
"git.perx.ru/perxis/perxis-go/pkg/spaces"
"git.perx.ru/perxis/perxis-go/pkg/users"
)
type UserPrincipal struct {
id string
identity string
user *users.User
invalid bool
spaceID string
orgID string
users users.Users
members members.Members
hasMemberRole bool
memberRole members.Role
collaborators collaborators.Collaborators
spaces spaces.Spaces
environments environments.Environments
roles roles.Roles
}
func (u UserPrincipal) Format(f fmt.State, verb rune) {
f.Write([]byte(fmt.Sprintf("UserPrincipal{ID: '%s', Identity: '%s'}", u.id, u.identity)))
}
func (u *UserPrincipal) Space(spaceID string) SpaceAccessor {
u.spaceID = spaceID
u.orgID = ""
return u
}
func (u *UserPrincipal) getSpace(ctx context.Context, spaceID string) *spaces.Space {
if spaceID == "" {
return nil
}
space, _ := u.spaces.Get(WithSystem(ctx), spaceID)
return space
}
func (u UserPrincipal) Organization(orgID string) OrganizationAccessor {
u.orgID = orgID
return &u
}
func (u *UserPrincipal) GetID(ctx context.Context) string {
user := u.User(ctx)
if user == nil {
return ""
}
return user.ID
}
func (u *UserPrincipal) GetIdentity(ctx context.Context) string {
return u.identity
}
func (u *UserPrincipal) IsValid(ctx context.Context) bool {
if u == nil {
return false
}
return u.User(ctx) != nil
}
func (u *UserPrincipal) IsSystem(ctx context.Context) bool {
user := u.User(ctx)
if user != nil {
return user.IsSystem()
}
return false
}
func (u *UserPrincipal) IsManagementAllowed(ctx context.Context, spaceID string) error {
if !u.IsValid(ctx) {
return ErrAccessDenied
}
if u.IsSystem(ctx) {
return nil
}
if u.Member(ctx).IsPrivileged() {
return nil
}
if role := u.Role(ctx, spaceID); role != nil && role.AllowManagement {
return nil
}
return ErrAccessDenied
}
func (u *UserPrincipal) User(ctx context.Context) *users.User {
if u.invalid {
return nil
}
if u.user != nil {
return u.user
}
if u.users == nil {
u.invalid = true
return nil
}
var user *users.User
var err error
switch {
case u.id != "":
user, err = u.users.Get(WithSystem(ctx), u.id)
case u.identity != "":
ctx = WithSystem(ctx)
user, err = u.users.GetByIdentity(WithSystem(ctx), u.identity)
}
if err != nil || user == nil {
u.invalid = true
return nil
}
u.user = user
return u.user
}
func (u *UserPrincipal) Member(ctx context.Context) members.Role {
if u.hasMemberRole {
return u.memberRole
}
if u.members == nil || (u.orgID == "" && u.spaceID == "") {
u.hasMemberRole = true
return members.NotMember
}
if u.orgID == "" && u.spaceID != "" {
sp := u.getSpace(ctx, u.spaceID)
if sp == nil {
u.hasMemberRole = true
return members.NotMember
}
u.orgID = sp.OrgID
}
role, err := u.members.Get(WithSystem(ctx), u.orgID, u.GetID(ctx))
if err != nil {
role = members.NotMember
}
u.memberRole = role
u.hasMemberRole = true
return u.memberRole
}
// HasSpaceAccess проверяет, есть ли у пользователя доступ к пространству
// Пользователь имеет доступ к пространству если:
// - Является участником пространства (даже если его роль не существует)
// - Пространство позволяет доступ для не участников (есть роли AnonymousRole/AuthorizedRole/ViewRole)
// Deprecated :use HasAccess
func (u *UserPrincipal) HasSpaceAccess(ctx context.Context, spaceID string) bool {
res, _ := u.hasRole(ctx, spaceID)
return res
}
// HasAccess проверяет, есть ли у пользователя доступ к пространству
// Пользователь имеет доступ к пространству если:
// - Является участником пространства (даже если его роль не существует)
// - Пространство позволяет доступ для не участников (есть роли AnonymousRole/AuthorizedRole/ViewRole)
func (u *UserPrincipal) HasAccess(ctx context.Context, spaceID, orgID string) error {
if !u.IsValid(ctx) {
return ErrAccessDenied
}
if u.IsSystem(ctx) {
return nil
}
if spaceID != "" {
hasAllow, err := u.hasRole(ctx, spaceID)
if err != nil {
return err
}
if hasAllow {
return nil
}
}
if orgID != "" {
if u.Organization(orgID).Member(ctx).IsPrivileged() {
return nil
}
} else {
if u.Member(ctx).IsPrivileged() {
return nil
}
}
return ErrAccessDenied
}
func (u *UserPrincipal) hasRole(ctx context.Context, spaceID string) (bool, error) {
if u.spaceID == "" || spaceID == "" {
return false, nil
}
ctx = WithSystem(ctx)
if spaceID != u.spaceID {
_, cErr := u.collaborators.Get(ctx, spaceID, u.spaceID)
if cErr == nil {
return true, nil
}
_, rErr := u.roles.Get(ctx, spaceID, roles.ViewRole)
if rErr == nil {
return true, nil
}
if errors.Is(cErr, ErrNotFound) || errors.Is(rErr, ErrNotFound) {
if sp := u.getSpace(ctx, spaceID); sp == nil {
return false, ErrNotFound
}
}
return false, nil
}
_, cErr := u.collaborators.Get(ctx, spaceID, u.GetID(ctx))
if cErr == nil {
return true, nil
}
_, rErr := u.roles.Get(ctx, spaceID, roles.AuthorizedRole)
if rErr == nil {
return true, nil
}
if errors.Is(cErr, ErrNotFound) || errors.Is(rErr, ErrNotFound) {
if sp := u.getSpace(ctx, spaceID); sp == nil {
return false, ErrNotFound
}
}
return false, nil
}
func (u *UserPrincipal) getRoleID(ctx context.Context, spaceID string) string {
if u.spaceID == "" || spaceID == "" {
return ""
}
ctx = WithSystem(ctx)
if spaceID != u.spaceID {
rID, err := u.collaborators.Get(ctx, spaceID, u.spaceID)
if err != nil {
rID = roles.ViewRole
}
return rID
}
if roleID, err := u.collaborators.Get(ctx, spaceID, u.GetID(ctx)); err == nil {
return roleID
}
return roles.AuthorizedRole
}
func (u *UserPrincipal) Role(ctx context.Context, spaceID string) *roles.Role {
if roleID := u.getRoleID(ctx, spaceID); roleID != "" {
role, _ := u.roles.Get(WithSystem(ctx), spaceID, roleID)
return role
}
return nil
}
func (u *UserPrincipal) Rules(ctx context.Context, spaceID, envID string) permission.Ruleset {
if spaceID == "" || envID == "" {
return nil
}
if u.spaceID == spaceID && (u.IsSystem(ctx) || u.Member(ctx).IsPrivileged()) {
return permission.PrivilegedRuleset{}
}
role := u.Role(ctx, spaceID)
if role == nil {
return nil
}
if !hasEnvironmentAccess(ctx, u.environments, role, envID) {
return nil
}
return role.Rules
}
func IsValidUser(ctx context.Context, p Principal) bool {
if p == nil {
return false
}
if u, ok := p.(*UserPrincipal); ok {
return u.IsValid(ctx)
}
return false
}
func User(ctx context.Context, p Principal) *users.User {
if u, ok := p.(*UserPrincipal); ok {
return u.User(ctx)
}
return nil
}
func (u *UserPrincipal) HasEnvironmentAccess(ctx context.Context, spaceID, env string) bool {
return hasEnvironmentAccess(ctx, u.environments, u.Role(ctx, spaceID), env)
}
...@@ -39,27 +39,35 @@ func (r Role) CanAccessEnvironment(ctx context.Context, service environments.Env ...@@ -39,27 +39,35 @@ func (r Role) CanAccessEnvironment(ctx context.Context, service environments.Env
return false return false
} }
if r.AllowManagement {
return true
}
// Если явно не указаны доступные окружения - доступ по умолчанию к окружению master // Если явно не указаны доступные окружения - доступ по умолчанию к окружению master
if len(r.Environments) == 0 { if len(r.Environments) == 0 {
r.Environments = []string{environments.DefaultEnvironment} r.Environments = []string{environments.DefaultEnvironment}
} }
if data.Contains(envID, r.Environments) { for _, e := range r.Environments {
if envID == e || data.GlobMatch(envID, e) {
return true return true
} }
}
e, err := service.Get(ctx, spaceID, envID) env, err := service.Get(ctx, spaceID, envID)
if err != nil || e == nil { if err != nil || env == nil {
return false return false
} }
aliases := append(e.Aliases, e.ID) aliases := append(env.Aliases, env.ID)
for _, ce := range r.Environments { for _, e := range r.Environments {
if data.Contains(ce, aliases) { for _, a := range aliases {
if a == e || data.GlobMatch(a, e) {
return true return true
} }
} }
}
return false return false
} }
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Please register or to comment