Skip to content
Snippets Groups Projects
user.go 7.42 KiB
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"
	"git.perx.ru/perxis/perxis/services"
)

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 services.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 services.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, services.ErrNotFound) || errors.Is(rErr, services.ErrNotFound) {
			if sp := u.getSpace(ctx, spaceID); sp == nil {
				return false, services.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, services.ErrNotFound) || errors.Is(rErr, services.ErrNotFound) {
		if sp := u.getSpace(ctx, spaceID); sp == nil {
			return false, services.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)
}