242 lines
8.5 KiB
Go
242 lines
8.5 KiB
Go
// Vikunja is a to-do list application to facilitate your life.
|
|
// Copyright 2018-present Vikunja and contributors. All rights reserved.
|
|
//
|
|
// This program is free software: you can redistribute it and/or modify
|
|
// it under the terms of the GNU Affero General Public License as published by
|
|
// the Free Software Foundation, either version 3 of the License, or
|
|
// (at your option) any later version.
|
|
//
|
|
// This program is distributed in the hope that it will be useful,
|
|
// but WITHOUT ANY WARRANTY; without even the implied warranty of
|
|
// MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
|
// GNU Affero General Public License for more details.
|
|
//
|
|
// You should have received a copy of the GNU Affero General Public License
|
|
// along with this program. If not, see <https://www.gnu.org/licenses/>.
|
|
|
|
package models
|
|
|
|
import (
|
|
"time"
|
|
|
|
"code.vikunja.io/api/pkg/license"
|
|
"code.vikunja.io/api/pkg/user"
|
|
"code.vikunja.io/api/pkg/web"
|
|
|
|
"xorm.io/xorm"
|
|
)
|
|
|
|
// ProFeatureInstanceDefault stores the admin-set instance-wide default for a
|
|
// per-user toggleable pro feature. Without a row, the code default applies.
|
|
type ProFeatureInstanceDefault struct {
|
|
ID int64 `xorm:"bigint autoincr not null unique pk" json:"-"`
|
|
Feature string `xorm:"varchar(50) not null unique" json:"feature"`
|
|
Enabled bool `xorm:"not null" json:"enabled"`
|
|
Created time.Time `xorm:"created not null" json:"created"`
|
|
Updated time.Time `xorm:"updated not null" json:"updated"`
|
|
}
|
|
|
|
func (ProFeatureInstanceDefault) TableName() string {
|
|
return "pro_feature_instance_defaults"
|
|
}
|
|
|
|
// GetProFeatureInstanceDefaults returns all admin-set instance defaults keyed
|
|
// by feature string.
|
|
func GetProFeatureInstanceDefaults(s *xorm.Session) (map[string]bool, error) {
|
|
defaults := []*ProFeatureInstanceDefault{}
|
|
if err := s.Find(&defaults); err != nil {
|
|
return nil, err
|
|
}
|
|
out := make(map[string]bool, len(defaults))
|
|
for _, d := range defaults {
|
|
out[d.Feature] = d.Enabled
|
|
}
|
|
return out, nil
|
|
}
|
|
|
|
// SetProFeatureInstanceDefault upserts the instance-wide default for a feature.
|
|
func SetProFeatureInstanceDefault(s *xorm.Session, feature license.Feature, enabled bool) error {
|
|
existing := &ProFeatureInstanceDefault{}
|
|
has, err := s.Where("feature = ?", feature.String()).Get(existing)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
if has {
|
|
existing.Enabled = enabled
|
|
_, err = s.ID(existing.ID).Cols("enabled").Update(existing)
|
|
return err
|
|
}
|
|
_, err = s.Insert(&ProFeatureInstanceDefault{Feature: feature.String(), Enabled: enabled})
|
|
return err
|
|
}
|
|
|
|
// ResetProFeatureInstanceDefault removes the instance-wide default for a
|
|
// feature so the code default applies again.
|
|
func ResetProFeatureInstanceDefault(s *xorm.Session, feature license.Feature) error {
|
|
_, err := s.Where("feature = ?", feature.String()).Delete(&ProFeatureInstanceDefault{})
|
|
return err
|
|
}
|
|
|
|
// resolvePerUserProFeature resolves the per-user layer only (override →
|
|
// instance default → code default). The caller must already have checked the
|
|
// license layer.
|
|
func resolvePerUserProFeature(s *xorm.Session, u *user.User, feature license.Feature) (bool, error) {
|
|
if u != nil {
|
|
if v, ok := u.ProFeatureOverrides[feature.String()]; ok {
|
|
return v, nil
|
|
}
|
|
}
|
|
defaults, err := GetProFeatureInstanceDefaults(s)
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
if v, ok := defaults[feature.String()]; ok {
|
|
return v, nil
|
|
}
|
|
return license.PerUserDefault(feature), nil
|
|
}
|
|
|
|
// IsProFeatureEnabledForUser returns whether a feature is effectively enabled
|
|
// for the given user: the instance license must include it, and for per-user
|
|
// toggleable features the user override / instance default / code default
|
|
// chain must resolve to enabled. The user must carry its DB state — claim-
|
|
// derived users miss ProFeatureOverrides.
|
|
func IsProFeatureEnabledForUser(s *xorm.Session, u *user.User, feature license.Feature) (bool, error) {
|
|
if !license.IsFeatureEnabled(feature) {
|
|
return false, nil
|
|
}
|
|
if !license.IsPerUserToggleable(feature) {
|
|
return true, nil
|
|
}
|
|
return resolvePerUserProFeature(s, u, feature)
|
|
}
|
|
|
|
// IsProFeatureEnabledForAuth resolves the authenticated principal and checks
|
|
// the feature for it. Link shares carry no per-user override, so only the
|
|
// instance default / code default chain applies to them.
|
|
func IsProFeatureEnabledForAuth(s *xorm.Session, a web.Auth, feature license.Feature) (bool, error) {
|
|
if !license.IsFeatureEnabled(feature) {
|
|
return false, nil
|
|
}
|
|
if !license.IsPerUserToggleable(feature) {
|
|
return true, nil
|
|
}
|
|
|
|
var u *user.User
|
|
if _, isUser := a.(*user.User); isUser {
|
|
// Re-read from the DB: the auth user is claim-derived and does not
|
|
// include ProFeatureOverrides.
|
|
fresh, err := user.GetUserByID(s, a.GetID())
|
|
if err != nil {
|
|
return false, err
|
|
}
|
|
u = fresh
|
|
}
|
|
return resolvePerUserProFeature(s, u, feature)
|
|
}
|
|
|
|
// EffectiveProFeaturesForUser returns the pro features effectively enabled for
|
|
// the given user, for exposure to clients.
|
|
func EffectiveProFeaturesForUser(s *xorm.Session, u *user.User) ([]license.Feature, error) {
|
|
enabled := license.EnabledProFeatures()
|
|
out := make([]license.Feature, 0, len(enabled))
|
|
for _, f := range enabled {
|
|
if license.IsPerUserToggleable(f) {
|
|
on, err := resolvePerUserProFeature(s, u, f)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
if !on {
|
|
continue
|
|
}
|
|
}
|
|
out = append(out, f)
|
|
}
|
|
return out, nil
|
|
}
|
|
|
|
// ProFeatureState describes one pro feature for the admin panel: its license
|
|
// state and, for per-user toggleable features, the effective instance default.
|
|
type ProFeatureState struct {
|
|
Feature string `json:"feature" doc:"The feature key, e.g. time_tracking."`
|
|
Licensed bool `json:"licensed" doc:"Whether the instance license includes this feature."`
|
|
PerUserToggleable bool `json:"per_user_toggleable" doc:"Whether admins can grant or revoke this feature per user. Instance-wide features are always on for everyone when licensed."`
|
|
DefaultEnabled bool `json:"default_enabled" doc:"The default for users without an override. Only meaningful for per-user toggleable features."`
|
|
DefaultSource string `json:"default_source" enum:"code,instance" doc:"Where the default comes from: the built-in code default or an admin-set instance default."`
|
|
}
|
|
|
|
// GetProFeatureStates returns the admin view of every known pro feature.
|
|
func GetProFeatureStates(s *xorm.Session) ([]*ProFeatureState, error) {
|
|
instanceDefaults, err := GetProFeatureInstanceDefaults(s)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
features := license.AllFeatures()
|
|
out := make([]*ProFeatureState, 0, len(features))
|
|
for _, f := range features {
|
|
st := &ProFeatureState{
|
|
Feature: f.String(),
|
|
Licensed: license.IsFeatureEnabled(f),
|
|
PerUserToggleable: license.IsPerUserToggleable(f),
|
|
}
|
|
if st.PerUserToggleable {
|
|
st.DefaultEnabled = license.PerUserDefault(f)
|
|
st.DefaultSource = "code"
|
|
if v, ok := instanceDefaults[f.String()]; ok {
|
|
st.DefaultEnabled = v
|
|
st.DefaultSource = "instance"
|
|
}
|
|
}
|
|
out = append(out, st)
|
|
}
|
|
return out, nil
|
|
}
|
|
|
|
// UserProFeatureState describes one per-user toggleable feature for a single user.
|
|
type UserProFeatureState struct {
|
|
Feature string `json:"feature" doc:"The feature key, e.g. time_tracking."`
|
|
Override *bool `json:"override" doc:"The admin-set override for this user, null when the instance default applies."`
|
|
Effective bool `json:"effective" doc:"Whether the feature is effectively enabled for this user, license included."`
|
|
}
|
|
|
|
// GetUserProFeatureStates returns the per-user toggleable features with the
|
|
// user's override and effective state.
|
|
func GetUserProFeatureStates(s *xorm.Session, u *user.User) ([]*UserProFeatureState, error) {
|
|
out := []*UserProFeatureState{}
|
|
for _, f := range license.AllFeatures() {
|
|
if !license.IsPerUserToggleable(f) {
|
|
continue
|
|
}
|
|
st := &UserProFeatureState{Feature: f.String()}
|
|
if v, ok := u.ProFeatureOverrides[f.String()]; ok {
|
|
override := v
|
|
st.Override = &override
|
|
}
|
|
effective, err := IsProFeatureEnabledForUser(s, u, f)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
st.Effective = effective
|
|
out = append(out, st)
|
|
}
|
|
return out, nil
|
|
}
|
|
|
|
// SetUserProFeatureOverride sets or clears (enabled == nil) the per-user
|
|
// override for a feature.
|
|
func SetUserProFeatureOverride(s *xorm.Session, u *user.User, feature license.Feature, enabled *bool) error {
|
|
if enabled == nil {
|
|
delete(u.ProFeatureOverrides, feature.String())
|
|
} else {
|
|
if u.ProFeatureOverrides == nil {
|
|
u.ProFeatureOverrides = map[string]bool{}
|
|
}
|
|
u.ProFeatureOverrides[feature.String()] = *enabled
|
|
}
|
|
_, err := s.Where("id = ?", u.ID).
|
|
Cols("pro_feature_overrides").
|
|
Update(u)
|
|
return err
|
|
}
|