Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Make the licensing match what's advertised #8593

Merged
merged 6 commits into from
Mar 14, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion components/licensor/ee/cmd/validate.go
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ var validateCmd = &cobra.Command{
domain, _ := cmd.Flags().GetString("domain")
licensorType, _ := cmd.Flags().GetString("licensor")

var e licensor.Evaluator
var e *licensor.Evaluator
switch licensorType {
case string(licensor.LicenseTypeReplicated):
e = licensor.NewReplicatedEvaluator(domain)
Expand Down
63 changes: 11 additions & 52 deletions components/licensor/ee/pkg/licensor/gitpod.go
Original file line number Diff line number Diff line change
Expand Up @@ -14,73 +14,31 @@ import (
"time"
)

// GitpodEvaluator determines what a license allows for
type GitpodEvaluator struct {
invalid string
lic LicensePayload
}

// Validate returns false if the license isn't valid and a message explaining why that is.
func (e *GitpodEvaluator) Validate() (msg string, valid bool) {
if e.invalid == "" {
return "", true
}

return e.invalid, false
}

// Enabled determines if a feature is enabled by the license
func (e *GitpodEvaluator) Enabled(feature Feature) bool {
if e.invalid != "" {
return false
}

_, ok := e.lic.Level.allowance().Features[feature]
return ok
}

// HasEnoughSeats returns true if the license supports at least the give amount of seats
func (e *GitpodEvaluator) HasEnoughSeats(seats int) bool {
if e.invalid != "" {
return false
}

return e.lic.Seats == 0 || seats <= e.lic.Seats
}

// Inspect returns the license information this evaluator holds.
// This function is intended for transparency/debugging purposes only and must
// never be used to determine feature eligibility under a license. All code making
// those kinds of decisions must be part of the Evaluator.
func (e *GitpodEvaluator) Inspect() LicensePayload {
return e.lic
}

// NewGitpodEvaluator produces a new license evaluator from a license key
func NewGitpodEvaluator(key []byte, domain string) (res *GitpodEvaluator) {
func NewGitpodEvaluator(key []byte, domain string) (res *Evaluator) {
if len(key) == 0 {
// fallback to the default license
return &GitpodEvaluator{
return &Evaluator{
lic: defaultLicense,
}
}

deckey := make([]byte, base64.StdEncoding.DecodedLen(len(key)))
n, err := base64.StdEncoding.Decode(deckey, key)
if err != nil {
return &GitpodEvaluator{invalid: fmt.Sprintf("cannot decode key: %q", err)}
return &Evaluator{invalid: fmt.Sprintf("cannot decode key: %q", err)}
}
deckey = deckey[:n]

var lic licensePayload
err = json.Unmarshal(deckey, &lic)
if err != nil {
return &GitpodEvaluator{invalid: fmt.Sprintf("cannot unmarshal key: %q", err)}
return &Evaluator{invalid: fmt.Sprintf("cannot unmarshal key: %q", err)}
}

keyWoSig, err := json.Marshal(lic.LicensePayload)
if err != nil {
return &GitpodEvaluator{invalid: fmt.Sprintf("cannot remarshal key: %q", err)}
return &Evaluator{invalid: fmt.Sprintf("cannot remarshal key: %q", err)}
}
hashed := sha256.Sum256(keyWoSig)

Expand All @@ -91,18 +49,19 @@ func NewGitpodEvaluator(key []byte, domain string) (res *GitpodEvaluator) {
}
}
if err != nil {
return &GitpodEvaluator{invalid: fmt.Sprintf("cannot verify key: %q", err)}
return &Evaluator{invalid: fmt.Sprintf("cannot verify key: %q", err)}
}

if !matchesDomain(lic.Domain, domain) {
return &GitpodEvaluator{invalid: "wrong domain"}
return &Evaluator{invalid: "wrong domain"}
}

if lic.ValidUntil.Before(time.Now()) {
return &GitpodEvaluator{invalid: "not valid anymore"}
return &Evaluator{invalid: "not valid anymore"}
}

return &GitpodEvaluator{
lic: lic.LicensePayload,
return &Evaluator{
lic: lic.LicensePayload,
allowFallback: false, // Gitpod licenses cannot fallback - assume these are always paid-for
}
}
81 changes: 73 additions & 8 deletions components/licensor/ee/pkg/licensor/licensor.go
Original file line number Diff line number Diff line change
Expand Up @@ -87,9 +87,8 @@ type allowance struct {

var allowanceMap = map[LicenseLevel]allowance{
LevelTeam: {
PrebuildTime: 50 * time.Hour,
Features: featureSet{
FeaturePrebuild: struct{}{},
FeatureAdminDashboard: struct{}{},
},
},
LevelEnterprise: {
Expand All @@ -115,9 +114,18 @@ func (lvl LicenseLevel) allowance() allowance {
return a
}

// Fallback license is used when the instance exceeds the number of licenses - it allows limited access
var fallbackLicense = LicensePayload{
ID: "fallback-license",
Level: LevelTeam,
Seats: 0,
// Domain, ValidUntil are free for all
}

// Default license is used when no valid license is given - it allows full access up to 10 users
var defaultLicense = LicensePayload{
ID: "default-license",
Level: LevelTeam,
Level: LevelEnterprise,
Seats: 10,
// Domain, ValidUntil are free for all
}
Expand All @@ -140,11 +148,68 @@ func matchesDomain(pattern, domain string) bool {
return false
}

type Evaluator interface {
Enabled(feature Feature) bool
HasEnoughSeats(seats int) bool
Inspect() LicensePayload
Validate() (msg string, valid bool)
// Evaluator determines what a license allows for
type Evaluator struct {
invalid string
allowFallback bool // Paid licenses cannot fallback and prevent additional signups
lic LicensePayload
}

// Validate returns false if the license isn't valid and a message explaining why that is.
func (e *Evaluator) Validate() (msg string, valid bool) {
if e.invalid == "" {
return "", true
}

return e.invalid, false
}

// Enabled determines if a feature is enabled by the license
func (e *Evaluator) Enabled(feature Feature, seats int) bool {
if e.invalid != "" {
return false
}

var ok bool
if e.hasEnoughSeats(seats) {
// License has enough seats available - evaluate this license
_, ok = e.lic.Level.allowance().Features[feature]
} else if e.allowFallback {
// License has run out of seats - use the fallback license
_, ok = fallbackLicense.Level.allowance().Features[feature]
}

return ok
}

// hasEnoughSeats returns true if the license supports at least the give amount of seats
func (e *Evaluator) hasEnoughSeats(seats int) bool {
if e.invalid != "" {
return false
}

return e.lic.Seats == 0 || seats <= e.lic.Seats
}

// HasEnoughSeats is the public method to hasEnoughSeats. Will use fallback license if allowable
func (e *Evaluator) HasEnoughSeats(seats int) bool {
if e.invalid != "" {
return false
}

if !e.allowFallback {
return e.hasEnoughSeats(seats)
}
// There is always more space if can use a fallback license
return true
}

// Inspect returns the license information this evaluator holds.
// This function is intended for transparency/debugging purposes only and must
// never be used to determine feature eligibility under a license. All code making
// those kinds of decisions must be part of the Evaluator.
func (e *Evaluator) Inspect() LicensePayload {
return e.lic
}

// Sign signs a license so that it can be used with the evaluator
Expand Down
Loading